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 = 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 = None
     26 # Is Tcl available at all?

     27 have_tcl = True
     28 # path to PCbuild directory

     29 PCBUILD="PCbuild"
     30 # msvcrt version

     31 MSVCR = "90"
     32 # Name of certificate in default store to sign MSI with

     33 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(lib_file, def_file, dll_file, mingw_lib):
    136     warning = "WARNING: %s - libpythonXX.a not built"
    137     nm = find_executable('nm')
    138     dlltool = find_executable('dlltool')
    139 
    140     if not nm or not dlltool:
    141         print warning % "nm and/or dlltool were not found"
    142         return False
    143 
    144     nm_command = '%s -Cs %s' % (nm, lib_file)
    145     dlltool_command = "%s --dllname %s --def %s --output-lib %s" % \
    146         (dlltool, dll_file, def_file, mingw_lib)
    147     export_match = re.compile(r"^_imp__(.*) in python\d+\.dll").match
    148 
    149     f = open(def_file,'w')
    150     print >>f, "LIBRARY %s" % dll_file
    151     print >>f, "EXPORTS"
    152 
    153     nm_pipe = os.popen(nm_command)
    154     for line in nm_pipe.readlines():
    155         m = export_match(line)
    156         if m:
    157             print >>f, m.group(1)
    158     f.close()
    159     exit = nm_pipe.close()
    160 
    161     if exit:
    162         print warning % "nm did not run successfully"
    163         return False
    164 
    165     if os.system(dlltool_command) != 0:
    166         print warning % "dlltool did not run successfully"
    167         return False
    168 
    169     return True
    170 
    171 # Target files (.def and .a) go in PCBuild directory

    172 lib_file = os.path.join(srcdir, PCBUILD, "python%s%s.lib" % (major, minor))
    173 def_file = os.path.join(srcdir, PCBUILD, "python%s%s.def" % (major, minor))
    174 dll_file = "python%s%s.dll" % (major, minor)
    175 mingw_lib = os.path.join(srcdir, PCBUILD, "libpython%s%s.a" % (major, minor))
    176 
    177 have_mingw = build_mingw_lib(lib_file, def_file, dll_file, mingw_lib)
    178 
    179 # Determine the target architecture

    180 dll_path = os.path.join(srcdir, PCBUILD, dll_file)
    181 msilib.set_arch_from_file(dll_path)
    182 if msilib.pe_type(dll_path) != msilib.pe_type("msisupport.dll"):
    183     raise SystemError, "msisupport.dll for incorrect architecture"
    184 if msilib.Win64:
    185     upgrade_code = upgrade_code_64
    186     # Bump the last digit of the code by one, so that 32-bit and 64-bit

    187     # releases get separate product codes

    188     digit = hex((int(product_code[-2],16)+1)%16)[-1]
    189     product_code = product_code[:-2] + digit + '}'
    190 
    191 if testpackage:
    192     ext = 'px'
    193     testprefix = 'x'
    194 else:
    195     ext = 'py'
    196     testprefix = ''
    197 
    198 if msilib.Win64:
    199     SystemFolderName = "[System64Folder]"
    200     registry_component = 4|256
    201 else:
    202     SystemFolderName = "[SystemFolder]"
    203     registry_component = 4
    204 
    205 msilib.reset()
    206 
    207 # condition in which to install pythonxy.dll in system32:

    208 # a) it is Windows 9x or

    209 # b) it is NT, the user is privileged, and has chosen per-machine installation

    210 sys32cond = "(Windows9x or (Privileged and ALLUSERS))"
    211 
    212 def build_database():
    213     """Generate an empty database, with just the schema and the
    214     Summary information stream."""
    215     if snapshot:
    216         uc = upgrade_code_snapshot
    217     else:
    218         uc = upgrade_code
    219     if msilib.Win64:
    220         productsuffix = " (64-bit)"
    221     else:
    222         productsuffix = ""
    223     # schema represents the installer 2.0 database schema.

    224     # sequence is the set of standard sequences

    225     # (ui/execute, admin/advt/install)

    226     msiname = "python-%s%s.msi" % (full_current_version, msilib.arch_ext)
    227     db = msilib.init_database(msiname,
    228                   schema, ProductName="Python "+full_current_version+productsuffix,
    229                   ProductCode=product_code,
    230                   ProductVersion=current_version,
    231                   Manufacturer=u"Python Software Foundation",
    232                   request_uac = True)
    233     # The default sequencing of the RemoveExistingProducts action causes

    234     # removal of files that got just installed. Place it after

    235     # InstallInitialize, so we first uninstall everything, but still roll

    236     # back in case the installation is interrupted

    237     msilib.change_sequence(sequence.InstallExecuteSequence,
    238                            "RemoveExistingProducts", 1510)
    239     msilib.add_tables(db, sequence)
    240     # We cannot set ALLUSERS in the property table, as this cannot be

    241     # reset if the user choses a per-user installation. Instead, we

    242     # maintain WhichUsers, which can be "ALL" or "JUSTME". The UI manages

    243     # this property, and when the execution starts, ALLUSERS is set

    244     # accordingly.

    245     add_data(db, "Property", [("UpgradeCode", uc),
    246                               ("WhichUsers", "ALL"),
    247                               ("ProductLine", "Python%s%s" % (major, minor)),
    248                              ])
    249     db.Commit()
    250     return db, msiname
    251 
    252 def remove_old_versions(db):
    253     "Fill the upgrade table."
    254     start = "%s.%s.0" % (major, minor)
    255     # This requests that feature selection states of an older

    256     # installation should be forwarded into this one. Upgrading

    257     # requires that both the old and the new installation are

    258     # either both per-machine or per-user.

    259     migrate_features = 1
    260     # See "Upgrade Table". We remove releases with the same major and

    261     # minor version. For an snapshot, we remove all earlier snapshots. For

    262     # a release, we remove all snapshots, and all earlier releases.

    263     if snapshot:
    264         add_data(db, "Upgrade",
    265             [(upgrade_code_snapshot, start,
    266               current_version,
    267               None,                     # Ignore language

    268               migrate_features,
    269               None,                     # Migrate ALL features

    270               "REMOVEOLDSNAPSHOT")])
    271         props = "REMOVEOLDSNAPSHOT"
    272     else:
    273         add_data(db, "Upgrade",
    274             [(upgrade_code, start, current_version,
    275               None, migrate_features, None, "REMOVEOLDVERSION"),
    276              (upgrade_code_snapshot, start, "%s.%d.0" % (major, int(minor)+1),
    277               None, migrate_features, None, "REMOVEOLDSNAPSHOT")])
    278         props = "REMOVEOLDSNAPSHOT;REMOVEOLDVERSION"
    279 
    280     props += ";TARGETDIR;DLLDIR"
    281     # Installer collects the product codes of the earlier releases in

    282     # these properties. In order to allow modification of the properties,

    283     # they must be declared as secure. See "SecureCustomProperties Property"

    284     add_data(db, "Property", [("SecureCustomProperties", props)])
    285 
    286 class PyDialog(Dialog):
    287     """Dialog class with a fixed layout: controls at the top, then a ruler,
    288     then a list of buttons: back, next, cancel. Optionally a bitmap at the
    289     left."""
    290     def __init__(self, *args, **kw):
    291         """Dialog(database, name, x, y, w, h, attributes, title, first,
    292         default, cancel, bitmap=true)"""
    293         Dialog.__init__(self, *args)
    294         ruler = self.h - 36
    295         bmwidth = 152*ruler/328
    296         if kw.get("bitmap", True):
    297             self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin")
    298         self.line("BottomLine", 0, ruler, self.w, 0)
    299 
    300     def title(self, title):
    301         "Set the title text of the dialog at the top."
    302         # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix,

    303         # text, in VerdanaBold10

    304         self.text("Title", 135, 10, 220, 60, 0x30003,
    305                   r"{\VerdanaBold10}%s" % title)
    306 
    307     def back(self, title, next, name = "Back", active = 1):
    308         """Add a back button with a given title, the tab-next button,
    309         its name in the Control table, possibly initially disabled.
    310 
    311         Return the button, so that events can be associated"""
    312         if active:
    313             flags = 3 # Visible|Enabled

    314         else:
    315             flags = 1 # Visible

    316         return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next)
    317 
    318     def cancel(self, title, next, name = "Cancel", active = 1):
    319         """Add a cancel button with a given title, the tab-next button,
    320         its name in the Control table, possibly initially disabled.
    321 
    322         Return the button, so that events can be associated"""
    323         if active:
    324             flags = 3 # Visible|Enabled

    325         else:
    326             flags = 1 # Visible

    327         return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next)
    328 
    329     def next(self, title, next, name = "Next", active = 1):
    330         """Add a Next button with a given title, the tab-next button,
    331         its name in the Control table, possibly initially disabled.
    332 
    333         Return the button, so that events can be associated"""
    334         if active:
    335             flags = 3 # Visible|Enabled

    336         else:
    337             flags = 1 # Visible

    338         return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next)
    339 
    340     def xbutton(self, name, title, next, xpos):
    341         """Add a button with a given title, the tab-next button,
    342         its name in the Control table, giving its x position; the
    343         y-position is aligned with the other buttons.
    344 
    345         Return the button, so that events can be associated"""
    346         return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next)
    347 
    348 def add_ui(db):
    349     x = y = 50
    350     w = 370
    351     h = 300
    352     title = "[ProductName] Setup"
    353 
    354     # see "Dialog Style Bits"

    355     modal = 3      # visible | modal

    356     modeless = 1   # visible

    357     track_disk_space = 32
    358 
    359     add_data(db, 'ActionText', uisample.ActionText)
    360     add_data(db, 'UIText', uisample.UIText)
    361 
    362     # Bitmaps

    363     if not os.path.exists(srcdir+r"\PC\python_icon.exe"):
    364         raise "Run icons.mak in PC directory"
    365     add_data(db, "Binary",
    366              [("PythonWin", msilib.Binary(r"%s\PCbuild\installer.bmp" % srcdir)), # 152x328 pixels

    367               ("py.ico",msilib.Binary(srcdir+r"\PC\py.ico")),
    368              ])
    369     add_data(db, "Icon",
    370              [("python_icon.exe", msilib.Binary(srcdir+r"\PC\python_icon.exe"))])
    371 
    372     # Scripts

    373     # CheckDir sets TargetExists if TARGETDIR exists.

    374     # UpdateEditIDLE sets the REGISTRY.tcl component into

    375     # the installed/uninstalled state according to both the

    376     # Extensions and TclTk features.

    377     if os.system("nmake /nologo /c /f msisupport.mak") != 0:
    378         raise "'nmake /f msisupport.mak' failed"
    379     add_data(db, "Binary", [("Script", msilib.Binary("msisupport.dll"))])
    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'-Wi "[TARGETDIR]Lib\compileall.py" -f -x "bad_coding|badsyntax|site-packages|py3_" "[TARGETDIR]Lib"'
    412     lib2to3args = r'-c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"'
    413     # See "CustomAction Table"

    414     add_data(db, "CustomAction", [
    415         # msidbCustomActionTypeFirstSequence + msidbCustomActionTypeTextData + msidbCustomActionTypeProperty

    416         # See "Custom Action Type 51",

    417         # "Custom Action Execution Scheduling Options"

    418         ("InitialTargetDir", 307, "TARGETDIR",
    419          "[WindowsVolume]Python%s%s" % (major, minor)),
    420         ("SetDLLDirToTarget", 307, "DLLDIR", "[TARGETDIR]"),
    421         ("SetDLLDirToSystem32", 307, "DLLDIR", SystemFolderName),
    422         # msidbCustomActionTypeExe + msidbCustomActionTypeSourceFile

    423         # See "Custom Action Type 18"

    424         ("CompilePyc", 18, "python.exe", compileargs),
    425         ("CompilePyo", 18, "python.exe", "-O "+compileargs),
    426         ("CompileGrammar", 18, "python.exe", lib2to3args),
    427         ])
    428 
    429     # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table"

    430     # Numbers indicate sequence; see sequence.py for how these action integrate

    431     add_data(db, "InstallUISequence",
    432              [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140),
    433               ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141),
    434               ("InitialTargetDir", 'TARGETDIR=""', 750),
    435               # In the user interface, assume all-users installation if privileged.

    436               ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751),
    437               ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752),
    438               ("SelectDirectoryDlg", "Not Installed", 1230),
    439               # XXX no support for resume installations yet

    440               #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240),

    441               ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250),
    442               ("ProgressDlg", None, 1280)])
    443     add_data(db, "AdminUISequence",
    444              [("InitialTargetDir", 'TARGETDIR=""', 750),
    445               ("SetDLLDirToTarget", 'DLLDIR=""', 751),
    446              ])
    447 
    448     # Execute Sequences

    449     add_data(db, "InstallExecuteSequence",
    450             [("InitialTargetDir", 'TARGETDIR=""', 750),
    451              ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751),
    452              ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752),
    453              ("UpdateEditIDLE", None, 1050),
    454              ("CompilePyc", "COMPILEALL", 6800),
    455              ("CompilePyo", "COMPILEALL", 6801),
    456              ("CompileGrammar", "COMPILEALL", 6802),
    457             ])
    458     add_data(db, "AdminExecuteSequence",
    459             [("InitialTargetDir", 'TARGETDIR=""', 750),
    460              ("SetDLLDirToTarget", 'DLLDIR=""', 751),
    461              ("CompilePyc", "COMPILEALL", 6800),
    462              ("CompilePyo", "COMPILEALL", 6801),
    463              ("CompileGrammar", "COMPILEALL", 6802),
    464             ])
    465 
    466     #####################################################################

    467     # Standard dialogs: FatalError, UserExit, ExitDialog

    468     fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title,
    469                  "Finish", "Finish", "Finish")
    470     fatal.title("[ProductName] Installer ended prematurely")
    471     fatal.back("< Back", "Finish", active = 0)
    472     fatal.cancel("Cancel", "Back", active = 0)
    473     fatal.text("Description1", 135, 70, 220, 80, 0x30003,
    474                "[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.")
    475     fatal.text("Description2", 135, 155, 220, 20, 0x30003,
    476                "Click the Finish button to exit the Installer.")
    477     c=fatal.next("Finish", "Cancel", name="Finish")
    478     # See "ControlEvent Table". Parameters are the event, the parameter

    479     # to the action, and optionally the condition for the event, and the order

    480     # of events.

    481     c.event("EndDialog", "Exit")
    482 
    483     user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title,
    484                  "Finish", "Finish", "Finish")
    485     user_exit.title("[ProductName] Installer was interrupted")
    486     user_exit.back("< Back", "Finish", active = 0)
    487     user_exit.cancel("Cancel", "Back", active = 0)
    488     user_exit.text("Description1", 135, 70, 220, 80, 0x30003,
    489                "[ProductName] setup was interrupted.  Your system has not been modified.  "
    490                "To install this program at a later time, please run the installation again.")
    491     user_exit.text("Description2", 135, 155, 220, 20, 0x30003,
    492                "Click the Finish button to exit the Installer.")
    493     c = user_exit.next("Finish", "Cancel", name="Finish")
    494     c.event("EndDialog", "Exit")
    495 
    496     exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title,
    497                          "Finish", "Finish", "Finish")
    498     exit_dialog.title("Completing the [ProductName] Installer")
    499     exit_dialog.back("< Back", "Finish", active = 0)
    500     exit_dialog.cancel("Cancel", "Back", active = 0)
    501     exit_dialog.text("Acknowledgements", 135, 95, 220, 120, 0x30003,
    502       "Special Windows thanks to:\n"
    503       "    Mark Hammond, without whose years of freely \n"
    504       "    shared Windows expertise, Python for Windows \n"
    505       "    would still be Python for DOS.")
    506 
    507     c = exit_dialog.text("warning", 135, 200, 220, 40, 0x30003,
    508             "{\\VerdanaRed9}Warning: Python 3.3.0 is the last "
    509             "Python release for Windows 2000.")
    510     c.condition("Hide", "VersionNT > 500")
    511 
    512     exit_dialog.text("Description", 135, 235, 220, 20, 0x30003,
    513                "Click the Finish button to exit the Installer.")
    514     c = exit_dialog.next("Finish", "Cancel", name="Finish")
    515     c.event("EndDialog", "Return")
    516 
    517     #####################################################################

    518     # Required dialog: FilesInUse, ErrorDlg

    519     inuse = PyDialog(db, "FilesInUse",
    520                      x, y, w, h,
    521                      19,                # KeepModeless|Modal|Visible

    522                      title,
    523                      "Retry", "Retry", "Retry", bitmap=False)
    524     inuse.text("Title", 15, 6, 200, 15, 0x30003,
    525                r"{\DlgFontBold8}Files in Use")
    526     inuse.text("Description", 20, 23, 280, 20, 0x30003,
    527                "Some files that need to be updated are currently in use.")
    528     inuse.text("Text", 20, 55, 330, 50, 3,
    529                "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.")
    530     inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess",
    531                   None, None, None)
    532     c=inuse.back("Exit", "Ignore", name="Exit")
    533     c.event("EndDialog", "Exit")
    534     c=inuse.next("Ignore", "Retry", name="Ignore")
    535     c.event("EndDialog", "Ignore")
    536     c=inuse.cancel("Retry", "Exit", name="Retry")
    537     c.event("EndDialog","Retry")
    538 
    539 
    540     # See "Error Dialog". See "ICE20" for the required names of the controls.

    541     error = Dialog(db, "ErrorDlg",
    542                    50, 10, 330, 101,
    543                    65543,       # Error|Minimize|Modal|Visible

    544                    title,
    545                    "ErrorText", None, None)
    546     error.text("ErrorText", 50,9,280,48,3, "")
    547     error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None)
    548     error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo")
    549     error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes")
    550     error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort")
    551     error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel")
    552     error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore")
    553     error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk")
    554     error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry")
    555 
    556     #####################################################################

    557     # Global "Query Cancel" dialog

    558     cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title,
    559                     "No", "No", "No")
    560     cancel.text("Text", 48, 15, 194, 30, 3,
    561                 "Are you sure you want to cancel [ProductName] installation?")
    562     cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
    563                    "py.ico", None, None)
    564     c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No")
    565     c.event("EndDialog", "Exit")
    566 
    567     c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes")
    568     c.event("EndDialog", "Return")
    569 
    570     #####################################################################

    571     # Global "Wait for costing" dialog

    572     costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title,
    573                      "Return", "Return", "Return")
    574     costing.text("Text", 48, 15, 194, 30, 3,
    575                  "Please wait while the installer finishes determining your disk space requirements.")
    576     costing.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
    577                     "py.ico", None, None)
    578     c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None)
    579     c.event("EndDialog", "Exit")
    580 
    581     #####################################################################

    582     # Preparation dialog: no user input except cancellation

    583     prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title,
    584                     "Cancel", "Cancel", "Cancel")
    585     prep.text("Description", 135, 70, 220, 40, 0x30003,
    586               "Please wait while the Installer prepares to guide you through the installation.")
    587     prep.title("Welcome to the [ProductName] Installer")
    588     c=prep.text("ActionText", 135, 110, 220, 20, 0x30003, "Pondering...")
    589     c.mapping("ActionText", "Text")
    590     c=prep.text("ActionData", 135, 135, 220, 30, 0x30003, None)
    591     c.mapping("ActionData", "Text")
    592     prep.back("Back", None, active=0)
    593     prep.next("Next", None, active=0)
    594     c=prep.cancel("Cancel", None)
    595     c.event("SpawnDialog", "CancelDlg")
    596 
    597     #####################################################################

    598     # Target directory selection

    599     seldlg = PyDialog(db, "SelectDirectoryDlg", x, y, w, h, modal, title,
    600                     "Next", "Next", "Cancel")
    601     seldlg.title("Select Destination Directory")
    602     c = seldlg.text("Existing", 135, 25, 235, 30, 0x30003,
    603                     "{\VerdanaRed9}This update will replace your existing [ProductLine] installation.")
    604     c.condition("Hide", 'REMOVEOLDVERSION="" and REMOVEOLDSNAPSHOT=""')
    605     seldlg.text("Description", 135, 50, 220, 40, 0x30003,
    606                "Please select a directory for the [ProductName] files.")
    607 
    608     seldlg.back("< Back", None, active=0)
    609     c = seldlg.next("Next >", "Cancel")
    610     c.event("DoAction", "CheckDir", "TargetExistsOk<>1", order=1)
    611     # If the target exists, but we found that we are going to remove old versions, don't bother

    612     # confirming that the target directory exists. Strictly speaking, we should determine that

    613     # the target directory is indeed the target of the product that we are going to remove, but

    614     # I don't know how to do that.

    615     c.event("SpawnDialog", "ExistingDirectoryDlg", 'TargetExists=1 and REMOVEOLDVERSION="" and REMOVEOLDSNAPSHOT=""', 2)
    616     c.event("SetTargetPath", "TARGETDIR", 'TargetExists=0 or REMOVEOLDVERSION<>"" or REMOVEOLDSNAPSHOT<>""', 3)
    617     c.event("SpawnWaitDialog", "WaitForCostingDlg", "CostingComplete=1", 4)
    618     c.event("NewDialog", "SelectFeaturesDlg", 'TargetExists=0 or REMOVEOLDVERSION<>"" or REMOVEOLDSNAPSHOT<>""', 5)
    619 
    620     c = seldlg.cancel("Cancel", "DirectoryCombo")
    621     c.event("SpawnDialog", "CancelDlg")
    622 
    623     seldlg.control("DirectoryCombo", "DirectoryCombo", 135, 70, 172, 80, 393219,
    624                    "TARGETDIR", None, "DirectoryList", None)
    625     seldlg.control("DirectoryList", "DirectoryList", 135, 90, 208, 136, 3, "TARGETDIR",
    626                    None, "PathEdit", None)
    627     seldlg.control("PathEdit", "PathEdit", 135, 230, 206, 16, 3, "TARGETDIR", None, "Next", None)
    628     c = seldlg.pushbutton("Up", 306, 70, 18, 18, 3, "Up", None)
    629     c.event("DirectoryListUp", "0")
    630     c = seldlg.pushbutton("NewDir", 324, 70, 30, 18, 3, "New", None)
    631     c.event("DirectoryListNew", "0")
    632 
    633     #####################################################################

    634     # SelectFeaturesDlg

    635     features = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal|track_disk_space,
    636                         title, "Tree", "Next", "Cancel")
    637     features.title("Customize [ProductName]")
    638     features.text("Description", 135, 35, 220, 15, 0x30003,
    639                   "Select the way you want features to be installed.")
    640     features.text("Text", 135,45,220,30, 3,
    641                   "Click on the icons in the tree below to change the way features will be installed.")
    642 
    643     c=features.back("< Back", "Next")
    644     c.event("NewDialog", "SelectDirectoryDlg")
    645 
    646     c=features.next("Next >", "Cancel")
    647     c.mapping("SelectionNoItems", "Enabled")
    648     c.event("SpawnDialog", "DiskCostDlg", "OutOfDiskSpace=1", order=1)
    649     c.event("EndDialog", "Return", "OutOfDiskSpace<>1", order=2)
    650 
    651     c=features.cancel("Cancel", "Tree")
    652     c.event("SpawnDialog", "CancelDlg")
    653 
    654     # The browse property is not used, since we have only a single target path (selected already)

    655     features.control("Tree", "SelectionTree", 135, 75, 220, 95, 7, "_BrowseProperty",
    656                      "Tree of selections", "Back", None)
    657 
    658     #c=features.pushbutton("Reset", 42, 243, 56, 17, 3, "Reset", "DiskCost")

    659     #c.mapping("SelectionNoItems", "Enabled")

    660     #c.event("Reset", "0")

    661 
    662     features.control("Box", "GroupBox", 135, 170, 225, 90, 1, None, None, None, None)
    663 
    664     c=features.xbutton("DiskCost", "Disk &Usage", None, 0.10)
    665     c.mapping("SelectionNoItems","Enabled")
    666     c.event("SpawnDialog", "DiskCostDlg")
    667 
    668     c=features.xbutton("Advanced", "Advanced", None, 0.30)
    669     c.event("SpawnDialog", "AdvancedDlg")
    670 
    671     c=features.text("ItemDescription", 140, 180, 210, 30, 3,
    672                   "Multiline description of the currently selected item.")
    673     c.mapping("SelectionDescription","Text")
    674 
    675     c=features.text("ItemSize", 140, 210, 210, 45, 3,
    676                     "The size of the currently selected item.")
    677     c.mapping("SelectionSize", "Text")
    678 
    679     #####################################################################

    680     # Disk cost

    681     cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title,
    682                     "OK", "OK", "OK", bitmap=False)
    683     cost.text("Title", 15, 6, 200, 15, 0x30003,
    684               "{\DlgFontBold8}Disk Space Requirements")
    685     cost.text("Description", 20, 20, 280, 20, 0x30003,
    686               "The disk space required for the installation of the selected features.")
    687     cost.text("Text", 20, 53, 330, 60, 3,
    688               "The highlighted volumes (if any) do not have enough disk space "
    689               "available for the currently selected features.  You can either "
    690               "remove some files from the highlighted volumes, or choose to "
    691               "install less features onto local drive(s), or select different "
    692               "destination drive(s).")
    693     cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223,
    694                  None, "{120}{70}{70}{70}{70}", None, None)
    695     cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return")
    696 
    697     #####################################################################

    698     # WhichUsers Dialog. Only available on NT, and for privileged users.

    699     # This must be run before FindRelatedProducts, because that will

    700     # take into account whether the previous installation was per-user

    701     # or per-machine. We currently don't support going back to this

    702     # dialog after "Next" was selected; to support this, we would need to

    703     # find how to reset the ALLUSERS property, and how to re-run

    704     # FindRelatedProducts.

    705     # On Windows9x, the ALLUSERS property is ignored on the command line

    706     # and in the Property table, but installer fails according to the documentation

    707     # if a dialog attempts to set ALLUSERS.

    708     whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title,
    709                         "AdminInstall", "Next", "Cancel")
    710     whichusers.title("Select whether to install [ProductName] for all users of this computer.")
    711     # A radio group with two options: allusers, justme

    712     g = whichusers.radiogroup("AdminInstall", 135, 60, 235, 80, 3,
    713                               "WhichUsers", "", "Next")
    714     g.condition("Disable", "VersionNT=600") # Not available on Vista and Windows 2008

    715     g.add("ALL", 0, 5, 150, 20, "Install for all users")
    716     g.add("JUSTME", 0, 25, 235, 20, "Install just for me (not available on Windows Vista)")
    717 
    718     whichusers.back("Back", None, active=0)
    719 
    720     c = whichusers.next("Next >", "Cancel")
    721     c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1)
    722     c.event("EndDialog", "Return", order = 2)
    723 
    724     c = whichusers.cancel("Cancel", "AdminInstall")
    725     c.event("SpawnDialog", "CancelDlg")
    726 
    727     #####################################################################

    728     # Advanced Dialog.

    729     advanced = PyDialog(db, "AdvancedDlg", x, y, w, h, modal, title,
    730                         "CompilePyc", "Ok", "Ok")
    731     advanced.title("Advanced Options for [ProductName]")
    732     # A radio group with two options: allusers, justme

    733     advanced.checkbox("CompilePyc", 135, 60, 230, 50, 3,
    734                       "COMPILEALL", "Compile .py files to byte code after installation", "Ok")
    735 
    736     c = advanced.cancel("Ok", "CompilePyc", name="Ok") # Button just has location of cancel button.

    737     c.event("EndDialog", "Return")
    738 
    739     #####################################################################

    740     # Existing Directory dialog

    741     dlg = Dialog(db, "ExistingDirectoryDlg", 50, 30, 200, 80, modal, title,
    742                    "No", "No", "No")
    743     dlg.text("Title", 10, 20, 180, 40, 3,
    744              "[TARGETDIR] exists. Are you sure you want to overwrite existing files?")
    745     c=dlg.pushbutton("Yes", 30, 60, 55, 17, 3, "Yes", "No")
    746     c.event("[TargetExists]", "0", order=1)
    747     c.event("[TargetExistsOk]", "1", order=2)
    748     c.event("EndDialog", "Return", order=3)
    749     c=dlg.pushbutton("No", 115, 60, 55, 17, 3, "No", "Yes")
    750     c.event("EndDialog", "Return")
    751 
    752     #####################################################################

    753     # Installation Progress dialog (modeless)

    754     progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title,
    755                         "Cancel", "Cancel", "Cancel", bitmap=False)
    756     progress.text("Title", 20, 15, 200, 15, 0x30003,
    757                   "{\DlgFontBold8}[Progress1] [ProductName]")
    758     progress.text("Text", 35, 65, 300, 30, 3,
    759                   "Please wait while the Installer [Progress2] [ProductName]. "
    760                   "This may take several minutes.")
    761     progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:")
    762 
    763     c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...")
    764     c.mapping("ActionText", "Text")
    765 
    766     #c=progress.text("ActionData", 35, 140, 300, 20, 3, None)

    767     #c.mapping("ActionData", "Text")

    768 
    769     c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537,
    770                        None, "Progress done", None, None)
    771     c.mapping("SetProgress", "Progress")
    772 
    773     progress.back("< Back", "Next", active=False)
    774     progress.next("Next >", "Cancel", active=False)
    775     progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg")
    776 
    777     # Maintenance type: repair/uninstall

    778     maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title,
    779                      "Next", "Next", "Cancel")
    780     maint.title("Welcome to the [ProductName] Setup Wizard")
    781     maint.text("BodyText", 135, 63, 230, 42, 3,
    782                "Select whether you want to repair or remove [ProductName].")
    783     g=maint.radiogroup("RepairRadioGroup", 135, 108, 230, 60, 3,
    784                         "MaintenanceForm_Action", "", "Next")
    785     g.add("Change", 0, 0, 200, 17, "&Change [ProductName]")
    786     g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]")
    787     g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]")
    788 
    789     maint.back("< Back", None, active=False)
    790     c=maint.next("Finish", "Cancel")
    791     # Change installation: Change progress dialog to "Change", then ask

    792     # for feature selection

    793     c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1)
    794     c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2)
    795 
    796     # Reinstall: Change progress dialog to "Repair", then invoke reinstall

    797     # Also set list of reinstalled features to "ALL"

    798     c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5)
    799     c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6)
    800     c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7)
    801     c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8)
    802 
    803     # Uninstall: Change progress to "Remove", then invoke uninstall

    804     # Also set list of removed features to "ALL"

    805     c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11)
    806     c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12)
    807     c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13)
    808     c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14)
    809 
    810     # Close dialog when maintenance action scheduled

    811     c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20)
    812     c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21)
    813 
    814     maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg")
    815 
    816 
    817 # See "Feature Table". The feature level is 1 for all features,

    818 # and the feature attributes are 0 for the DefaultFeature, and

    819 # FollowParent for all other features. The numbers are the Display

    820 # column.

    821 def add_features(db):
    822     # feature attributes:

    823     # msidbFeatureAttributesFollowParent == 2

    824     # msidbFeatureAttributesDisallowAdvertise == 8

    825     # Features that need to be installed with together with the main feature

    826     # (i.e. additional Python libraries) need to follow the parent feature.

    827     # Features that have no advertisement trigger (e.g. the test suite)

    828     # must not support advertisement

    829     global default_feature, tcltk, htmlfiles, tools, testsuite, ext_feature, private_crt
    830     default_feature = Feature(db, "DefaultFeature", "Python",
    831                               "Python Interpreter and Libraries",
    832                               1, directory = "TARGETDIR")
    833     shared_crt = Feature(db, "SharedCRT", "MSVCRT", "C Run-Time (system-wide)", 0,
    834                          level=0)
    835     private_crt = Feature(db, "PrivateCRT", "MSVCRT", "C Run-Time (private)", 0,
    836                           level=0)
    837     add_data(db, "Condition", [("SharedCRT", 1, sys32cond),
    838                                ("PrivateCRT", 1, "not "+sys32cond)])
    839     # We don't support advertisement of extensions

    840     ext_feature = Feature(db, "Extensions", "Register Extensions",
    841                           "Make this Python installation the default Python installation", 3,
    842                          parent = default_feature, attributes=2|8)
    843     if have_tcl:
    844         tcltk = Feature(db, "TclTk", "Tcl/Tk", "Tkinter, IDLE, pydoc", 5,
    845                     parent = default_feature, attributes=2)
    846     htmlfiles = Feature(db, "Documentation", "Documentation",
    847                         "Python HTMLHelp File", 7, parent = default_feature)
    848     tools = Feature(db, "Tools", "Utility Scripts",
    849                     "Python utility scripts (Tools/", 9,
    850                     parent = default_feature, attributes=2)
    851     testsuite = Feature(db, "Testsuite", "Test suite",
    852                         "Python test suite (Lib/test/)", 11,
    853                         parent = default_feature, attributes=2|8)
    854 
    855 def extract_msvcr90():
    856     # Find the redistributable files

    857     if msilib.Win64:
    858         arch = "amd64"
    859     else:
    860         arch = "x86"
    861     dir = os.path.join(os.environ['VS90COMNTOOLS'], r"..\..\VC\redist\%s\Microsoft.VC90.CRT" % arch)
    862 
    863     result = []
    864     installer = msilib.MakeInstaller()
    865     # omit msvcm90 and msvcp90, as they aren't really needed

    866     files = ["Microsoft.VC90.CRT.manifest", "msvcr90.dll"]
    867     for f in files:
    868         path = os.path.join(dir, f)
    869         kw = {'src':path}
    870         if f.endswith('.dll'):
    871             kw['version'] = installer.FileVersion(path, 0)
    872             kw['language'] = installer.FileVersion(path, 1)
    873         result.append((f, kw))
    874     return result
    875 
    876 def generate_license():
    877     import shutil, glob
    878     out = open("LICENSE.txt", "w")
    879     shutil.copyfileobj(open(os.path.join(srcdir, "LICENSE")), out)
    880     shutil.copyfileobj(open("crtlicense.txt"), out)
    881     for name, pat, file in (("bzip2","bzip2-*", "LICENSE"),
    882                       ("Berkeley DB", "db-*", "LICENSE"),
    883                       ("openssl", "openssl-*", "LICENSE"),
    884                       ("Tcl", "tcl8*", "license.terms"),
    885                       ("Tk", "tk8*", "license.terms"),
    886                       ("Tix", "tix-*", "license.terms")):
    887         out.write("\nThis copy of Python includes a copy of %s, which is licensed under the following terms:\n\n" % name)
    888         dirs = glob.glob(srcdir+"/../"+pat)
    889         if not dirs:
    890             raise ValueError, "Could not find "+srcdir+"/../"+pat
    891         if len(dirs) > 2:
    892             raise ValueError, "Multiple copies of "+pat
    893         dir = dirs[0]
    894         shutil.copyfileobj(open(os.path.join(dir, file)), out)
    895     out.close()
    896 
    897 
    898 class PyDirectory(Directory):
    899     """By default, all components in the Python installer
    900     can run from source."""
    901     def __init__(self, *args, **kw):
    902         if not kw.has_key("componentflags"):
    903             kw['componentflags'] = 2 #msidbComponentAttributesOptional

    904         Directory.__init__(self, *args, **kw)
    905 
    906 # See "File Table", "Component Table", "Directory Table",

    907 # "FeatureComponents Table"

    908 def add_files(db):
    909     cab = CAB("python")
    910     tmpfiles = []
    911     # Add all executables, icons, text files into the TARGETDIR component

    912     root = PyDirectory(db, cab, None, srcdir, "TARGETDIR", "SourceDir")
    913     default_feature.set_current()
    914     if not msilib.Win64:
    915         root.add_file("%s/w9xpopen.exe" % PCBUILD)
    916     root.add_file("README.txt", src="README")
    917     root.add_file("NEWS.txt", src="Misc/NEWS")
    918     generate_license()
    919     root.add_file("LICENSE.txt", src=os.path.abspath("LICENSE.txt"))
    920     root.start_component("python.exe", keyfile="python.exe")
    921     root.add_file("%s/python.exe" % PCBUILD)
    922     root.start_component("pythonw.exe", keyfile="pythonw.exe")
    923     root.add_file("%s/pythonw.exe" % PCBUILD)
    924 
    925     # msidbComponentAttributesSharedDllRefCount = 8, see "Component Table"

    926     dlldir = PyDirectory(db, cab, root, srcdir, "DLLDIR", ".")
    927 
    928     pydll = "python%s%s.dll" % (major, minor)
    929     pydllsrc = os.path.join(srcdir, PCBUILD, pydll)
    930     dlldir.start_component("DLLDIR", flags = 8, keyfile = pydll, uuid = pythondll_uuid)
    931     installer = msilib.MakeInstaller()
    932     pyversion = installer.FileVersion(pydllsrc, 0)
    933     if not snapshot:
    934         # For releases, the Python DLL has the same version as the

    935         # installer package.

    936         assert pyversion.split(".")[:3] == current_version.split(".")
    937     dlldir.add_file("%s/python%s%s.dll" % (PCBUILD, major, minor),
    938                     version=pyversion,
    939                     language=installer.FileVersion(pydllsrc, 1))
    940     DLLs = PyDirectory(db, cab, root, srcdir + "/" + PCBUILD, "DLLs", "DLLS|DLLs")
    941 
    942     # msvcr90.dll: Need to place the DLL and the manifest into the root directory,

    943     # plus another copy of the manifest in the DLLs directory, with the manifest

    944     # pointing to the root directory

    945     root.start_component("msvcr90", feature=private_crt)
    946     # Results are ID,keyword pairs

    947     manifest, crtdll = extract_msvcr90()
    948     root.add_file(manifest[0], **manifest[1])
    949     root.add_file(crtdll[0], **crtdll[1])
    950     # Copy the manifest

    951     # Actually, don't do that anymore - no DLL in DLLs should have a manifest

    952     # dependency on msvcr90.dll anymore, so this should not be necessary

    953     #manifest_dlls = manifest[0]+".root"

    954     #open(manifest_dlls, "w").write(open(manifest[1]['src']).read().replace("msvcr","../msvcr"))

    955     #DLLs.start_component("msvcr90_dlls", feature=private_crt)

    956     #DLLs.add_file(manifest[0], src=os.path.abspath(manifest_dlls))

    957 
    958     # Now start the main component for the DLLs directory;

    959     # no regular files have been added to the directory yet.

    960     DLLs.start_component()
    961 
    962     # Check if _ctypes.pyd exists

    963     have_ctypes = os.path.exists(srcdir+"/%s/_ctypes.pyd" % PCBUILD)
    964     if not have_ctypes:
    965         print "WARNING: _ctypes.pyd not found, ctypes will not be included"
    966         extensions.remove("_ctypes.pyd")
    967 
    968     # Add all .py files in Lib, except lib-tk, test

    969     dirs={}
    970     pydirs = [(root,"Lib")]
    971     while pydirs:
    972         # Commit every now and then, or else installer will complain

    973         db.Commit()
    974         parent, dir = pydirs.pop()
    975         if dir == ".svn" or dir.startswith("plat-"):
    976             continue
    977         elif dir in ["lib-tk", "idlelib", "Icons"]:
    978             if not have_tcl:
    979                 continue
    980             tcltk.set_current()
    981         elif dir in ['test', 'tests', 'data', 'output']:
    982             # test: Lib, Lib/email, Lib/bsddb, Lib/ctypes, Lib/sqlite3

    983             # tests: Lib/distutils

    984             # data: Lib/email/test

    985             # output: Lib/test

    986             testsuite.set_current()
    987         elif not have_ctypes and dir == "ctypes":
    988             continue
    989         else:
    990             default_feature.set_current()
    991         lib = PyDirectory(db, cab, parent, dir, dir, "%s|%s" % (parent.make_short(dir), dir))
    992         # Add additional files

    993         dirs[dir]=lib
    994         lib.glob("*.txt")
    995         if dir=='site-packages':
    996             lib.add_file("README.txt", src="README")
    997             continue
    998         files = lib.glob("*.py")
    999         files += lib.glob("*.pyw")
   1000         if files:
   1001             # Add an entry to the RemoveFile table to remove bytecode files.

   1002             lib.remove_pyc()
   1003         if dir.endswith('.egg-info'):
   1004             lib.add_file('entry_points.txt')
   1005             lib.add_file('PKG-INFO')
   1006             lib.add_file('top_level.txt')
   1007             lib.add_file('zip-safe')
   1008             continue
   1009         if dir=='test' and parent.physical=='Lib':
   1010             lib.add_file("185test.db")
   1011             lib.add_file("audiotest.au")
   1012             lib.add_file("cfgparser.1")
   1013             lib.add_file("sgml_input.html")
   1014             lib.add_file("testtar.tar")
   1015             lib.add_file("test_difflib_expect.html")
   1016             lib.add_file("check_soundcard.vbs")
   1017             lib.add_file("empty.vbs")
   1018             lib.add_file("Sine-1000Hz-300ms.aif")
   1019             lib.glob("*.uue")
   1020             lib.glob("*.pem")
   1021             lib.glob("*.pck")
   1022             lib.add_file("zipdir.zip")
   1023         if dir=='tests' and parent.physical=='distutils':
   1024             lib.add_file("Setup.sample")
   1025         if dir=='decimaltestdata':
   1026             lib.glob("*.decTest")
   1027         if dir=='xmltestdata':
   1028             lib.glob("*.xml")
   1029             lib.add_file("test.xml.out")
   1030         if dir=='output':
   1031             lib.glob("test_*")
   1032         if dir=='idlelib':
   1033             lib.glob("*.def")
   1034             lib.add_file("idle.bat")
   1035         if dir=="Icons":
   1036             lib.glob("*.gif")
   1037             lib.add_file("idle.icns")
   1038         if dir=="command" and parent.physical=="distutils":
   1039             lib.glob("wininst*.exe")
   1040         if dir=="setuptools":
   1041             lib.add_file("cli.exe")
   1042             lib.add_file("gui.exe")
   1043         if dir=="lib2to3":
   1044             lib.removefile("pickle", "*.pickle")
   1045         if dir=="data" and parent.physical=="test" and parent.basedir.physical=="email":
   1046             # This should contain all non-.svn files listed in subversion

   1047             for f in os.listdir(lib.absolute):
   1048                 if f.endswith(".txt") or f==".svn":continue
   1049                 if f.endswith(".au") or f.endswith(".gif"):
   1050                     lib.add_file(f)
   1051                 else:
   1052                     print "WARNING: New file %s in email/test/data" % f
   1053         for f in os.listdir(lib.absolute):
   1054             if os.path.isdir(os.path.join(lib.absolute, f)):
   1055                 pydirs.append((lib, f))
   1056     # Add DLLs

   1057     default_feature.set_current()
   1058     lib = DLLs
   1059     lib.add_file("py.ico", src=srcdir+"/PC/py.ico")
   1060     lib.add_file("pyc.ico", src=srcdir+"/PC/pyc.ico")
   1061     dlls = []
   1062     tclfiles = []
   1063     for f in extensions:
   1064         if f=="_tkinter.pyd":
   1065             continue
   1066         if not os.path.exists(srcdir + "/" + PCBUILD + "/" + f):
   1067             print "WARNING: Missing extension", f
   1068             continue
   1069         dlls.append(f)
   1070         lib.add_file(f)
   1071     # Add sqlite

   1072     if msilib.msi_type=="Intel64;1033":
   1073         sqlite_arch = "/ia64"
   1074     elif msilib.msi_type=="x64;1033":
   1075         sqlite_arch = "/amd64"
   1076         tclsuffix = "64"
   1077     else:
   1078         sqlite_arch = ""
   1079         tclsuffix = ""
   1080     lib.add_file("sqlite3.dll")
   1081     if have_tcl:
   1082         if not os.path.exists("%s/%s/_tkinter.pyd" % (srcdir, PCBUILD)):
   1083             print "WARNING: Missing _tkinter.pyd"
   1084         else:
   1085             lib.start_component("TkDLLs", tcltk)
   1086             lib.add_file("_tkinter.pyd")
   1087             dlls.append("_tkinter.pyd")
   1088             tcldir = os.path.normpath(srcdir+("/../tcltk%s/bin" % tclsuffix))
   1089             for f in glob.glob1(tcldir, "*.dll"):
   1090                 lib.add_file(f, src=os.path.join(tcldir, f))
   1091     # check whether there are any unknown extensions

   1092     for f in glob.glob1(srcdir+"/"+PCBUILD, "*.pyd"):
   1093         if f.endswith("_d.pyd"): continue # debug version

   1094         if f in dlls: continue
   1095         print "WARNING: Unknown extension", f
   1096 
   1097     # Add headers

   1098     default_feature.set_current()
   1099     lib = PyDirectory(db, cab, root, "include", "include", "INCLUDE|include")
   1100     lib.glob("*.h")
   1101     lib.add_file("pyconfig.h", src="../PC/pyconfig.h")
   1102     # Add import libraries

   1103     lib = PyDirectory(db, cab, root, PCBUILD, "libs", "LIBS|libs")
   1104     for f in dlls:
   1105         lib.add_file(f.replace('pyd','lib'))
   1106     lib.add_file('python%s%s.lib' % (major, minor))
   1107     # Add the mingw-format library

   1108     if have_mingw:
   1109         lib.add_file('libpython%s%s.a' % (major, minor))
   1110     if have_tcl:
   1111         # Add Tcl/Tk

   1112         tcldirs = [(root, '../tcltk%s/lib' % tclsuffix, 'tcl')]
   1113         tcltk.set_current()
   1114         while tcldirs:
   1115             parent, phys, dir = tcldirs.pop()
   1116             lib = PyDirectory(db, cab, parent, phys, dir, "%s|%s" % (parent.make_short(dir), dir))
   1117             if not os.path.exists(lib.absolute):
   1118                 continue
   1119             for f in os.listdir(lib.absolute):
   1120                 if os.path.isdir(os.path.join(lib.absolute, f)):
   1121                     tcldirs.append((lib, f, f))
   1122                 else:
   1123                     lib.add_file(f)
   1124     # Add tools

   1125     tools.set_current()
   1126     tooldir = PyDirectory(db, cab, root, "Tools", "Tools", "TOOLS|Tools")
   1127     for f in ['i18n', 'pynche', 'Scripts', 'versioncheck', 'webchecker']:
   1128         lib = PyDirectory(db, cab, tooldir, f, f, "%s|%s" % (tooldir.make_short(f), f))
   1129         lib.glob("*.py")
   1130         lib.glob("*.pyw", exclude=['pydocgui.pyw'])
   1131         lib.remove_pyc()
   1132         lib.glob("*.txt")
   1133         if f == "pynche":
   1134             x = PyDirectory(db, cab, lib, "X", "X", "X|X")
   1135             x.glob("*.txt")
   1136         if os.path.exists(os.path.join(lib.absolute, "README")):
   1137             lib.add_file("README.txt", src="README")
   1138         if f == 'Scripts':
   1139             lib.add_file("2to3.py", src="2to3")
   1140             if have_tcl:
   1141                 lib.start_component("pydocgui.pyw", tcltk, keyfile="pydocgui.pyw")
   1142                 lib.add_file("pydocgui.pyw")
   1143     # Add documentation

   1144     htmlfiles.set_current()
   1145     lib = PyDirectory(db, cab, root, "Doc", "Doc", "DOC|Doc")
   1146     lib.start_component("documentation", keyfile=docfile)
   1147     lib.add_file(docfile, src="build/htmlhelp/"+docfile)
   1148 
   1149     cab.commit(db)
   1150 
   1151     for f in tmpfiles:
   1152         os.unlink(f)
   1153 
   1154 # See "Registry Table", "Component Table"

   1155 def add_registry(db):
   1156     # File extensions, associated with the REGISTRY.def component

   1157     # IDLE verbs depend on the tcltk feature.

   1158     # msidbComponentAttributesRegistryKeyPath = 4

   1159     # -1 for Root specifies "dependent on ALLUSERS property"

   1160     tcldata = []
   1161     if have_tcl:
   1162         tcldata = [
   1163             ("REGISTRY.tcl", msilib.gen_uuid(), "TARGETDIR", registry_component, None,
   1164              "py.IDLE")]
   1165     add_data(db, "Component",
   1166              # msidbComponentAttributesRegistryKeyPath = 4

   1167              [("REGISTRY", msilib.gen_uuid(), "TARGETDIR", registry_component, None,
   1168                "InstallPath"),
   1169               ("REGISTRY.doc", msilib.gen_uuid(), "TARGETDIR", registry_component, None,
   1170                "Documentation"),
   1171               ("REGISTRY.def", msilib.gen_uuid(), "TARGETDIR", registry_component,
   1172                None, None)] + tcldata)
   1173     # See "FeatureComponents Table".

   1174     # The association between TclTk and pythonw.exe is necessary to make ICE59

   1175     # happy, because the installer otherwise believes that the IDLE and PyDoc

   1176     # shortcuts might get installed without pythonw.exe being install. This

   1177     # is not true, since installing TclTk will install the default feature, which

   1178     # will cause pythonw.exe to be installed.

   1179     # REGISTRY.tcl is not associated with any feature, as it will be requested

   1180     # through a custom action

   1181     tcldata = []
   1182     if have_tcl:
   1183         tcldata = [(tcltk.id, "pythonw.exe")]
   1184     add_data(db, "FeatureComponents",
   1185              [(default_feature.id, "REGISTRY"),
   1186               (htmlfiles.id, "REGISTRY.doc"),
   1187               (ext_feature.id, "REGISTRY.def")] +
   1188               tcldata
   1189               )
   1190     # Extensions are not advertised. For advertised extensions,

   1191     # we would need separate binaries that install along with the

   1192     # extension.

   1193     pat = r"Software\Classes\%sPython.%sFile\shell\%s\command"
   1194     ewi = "Edit with IDLE"
   1195     pat2 = r"Software\Classes\%sPython.%sFile\DefaultIcon"
   1196     pat3 = r"Software\Classes\%sPython.%sFile"
   1197     pat4 = r"Software\Classes\%sPython.%sFile\shellex\DropHandler"
   1198     tcl_verbs = []
   1199     if have_tcl:
   1200         tcl_verbs=[
   1201              ("py.IDLE", -1, pat % (testprefix, "", ewi), "",
   1202               r'"[TARGETDIR]pythonw.exe" "[TARGETDIR]Lib\idlelib\idle.pyw" -e "%1"',
   1203               "REGISTRY.tcl"),
   1204              ("pyw.IDLE", -1, pat % (testprefix, "NoCon", ewi), "",
   1205               r'"[TARGETDIR]pythonw.exe" "[TARGETDIR]Lib\idlelib\idle.pyw" -e "%1"',
   1206               "REGISTRY.tcl"),
   1207         ]
   1208     add_data(db, "Registry",
   1209             [# Extensions

   1210              ("py.ext", -1, r"Software\Classes\."+ext, "",
   1211               "Python.File", "REGISTRY.def"),
   1212              ("pyw.ext", -1, r"Software\Classes\."+ext+'w', "",
   1213               "Python.NoConFile", "REGISTRY.def"),
   1214              ("pyc.ext", -1, r"Software\Classes\."+ext+'c', "",
   1215               "Python.CompiledFile", "REGISTRY.def"),
   1216              ("pyo.ext", -1, r"Software\Classes\."+ext+'o', "",
   1217               "Python.CompiledFile", "REGISTRY.def"),
   1218              # MIME types

   1219              ("py.mime", -1, r"Software\Classes\."+ext, "Content Type",
   1220               "text/plain", "REGISTRY.def"),
   1221              ("pyw.mime", -1, r"Software\Classes\."+ext+'w', "Content Type",
   1222               "text/plain", "REGISTRY.def"),
   1223              #Verbs

   1224              ("py.open", -1, pat % (testprefix, "", "open"), "",
   1225               r'"[TARGETDIR]python.exe" "%1" %*', "REGISTRY.def"),
   1226              ("pyw.open", -1, pat % (testprefix, "NoCon", "open"), "",
   1227               r'"[TARGETDIR]pythonw.exe" "%1" %*', "REGISTRY.def"),
   1228              ("pyc.open", -1, pat % (testprefix, "Compiled", "open"), "",
   1229               r'"[TARGETDIR]python.exe" "%1" %*', "REGISTRY.def"),
   1230              ] + tcl_verbs + [
   1231              #Icons

   1232              ("py.icon", -1, pat2 % (testprefix, ""), "",
   1233               r'[DLLs]py.ico', "REGISTRY.def"),
   1234              ("pyw.icon", -1, pat2 % (testprefix, "NoCon"), "",
   1235               r'[DLLs]py.ico', "REGISTRY.def"),
   1236              ("pyc.icon", -1, pat2 % (testprefix, "Compiled"), "",
   1237               r'[DLLs]pyc.ico', "REGISTRY.def"),
   1238              # Descriptions

   1239              ("py.txt", -1, pat3 % (testprefix, ""), "",
   1240               "Python File", "REGISTRY.def"),
   1241              ("pyw.txt", -1, pat3 % (testprefix, "NoCon"), "",
   1242               "Python File (no console)", "REGISTRY.def"),
   1243              ("pyc.txt", -1, pat3 % (testprefix, "Compiled"), "",
   1244               "Compiled Python File", "REGISTRY.def"),
   1245              # Drop Handler

   1246              ("py.drop", -1, pat4 % (testprefix, ""), "",
   1247               "{60254CA5-953B-11CF-8C96-00AA00B8708C}", "REGISTRY.def"),
   1248              ("pyw.drop", -1, pat4 % (testprefix, "NoCon"), "",
   1249               "{60254CA5-953B-11CF-8C96-00AA00B8708C}", "REGISTRY.def"),
   1250              ("pyc.drop", -1, pat4 % (testprefix, "Compiled"), "",
   1251               "{60254CA5-953B-11CF-8C96-00AA00B8708C}", "REGISTRY.def"),
   1252             ])
   1253 
   1254     # Registry keys

   1255     prefix = r"Software\%sPython\PythonCore\%s" % (testprefix, short_version)
   1256     add_data(db, "Registry",
   1257              [("InstallPath", -1, prefix+r"\InstallPath", "", "[TARGETDIR]", "REGISTRY"),
   1258               ("InstallGroup", -1, prefix+r"\InstallPath\InstallGroup", "",
   1259                "Python %s" % short_version, "REGISTRY"),
   1260               ("PythonPath", -1, prefix+r"\PythonPath", "",
   1261                r"[TARGETDIR]Lib;[TARGETDIR]DLLs;[TARGETDIR]Lib\lib-tk", "REGISTRY"),
   1262               ("Documentation", -1, prefix+r"\Help\Main Python Documentation", "",
   1263                "[TARGETDIR]Doc\\"+docfile , "REGISTRY.doc"),
   1264               ("Modules", -1, prefix+r"\Modules", "+", None, "REGISTRY"),
   1265               ("AppPaths", -1, r"Software\Microsoft\Windows\CurrentVersion\App Paths\Python.exe",
   1266                "", r"[TARGETDIR]Python.exe", "REGISTRY.def"),
   1267               ("DisplayIcon", -1,
   1268                r"Software\Microsoft\Windows\CurrentVersion\Uninstall\%s" % product_code,
   1269                "DisplayIcon", "[TARGETDIR]python.exe", "REGISTRY")
   1270               ])
   1271     # Shortcuts, see "Shortcut Table"

   1272     add_data(db, "Directory",
   1273              [("ProgramMenuFolder", "TARGETDIR", "."),
   1274               ("MenuDir", "ProgramMenuFolder", "PY%s%s|%sPython %s.%s" % (major,minor,testprefix,major,minor))])
   1275     add_data(db, "RemoveFile",
   1276              [("MenuDir", "TARGETDIR", None, "MenuDir", 2)])
   1277     tcltkshortcuts = []
   1278     if have_tcl:
   1279         tcltkshortcuts = [
   1280               ("IDLE", "MenuDir", "IDLE|IDLE (Python GUI)", "pythonw.exe",
   1281                tcltk.id, r'"[TARGETDIR]Lib\idlelib\idle.pyw"', None, None, "python_icon.exe", 0, None, "TARGETDIR"),
   1282               ("PyDoc", "MenuDir", "MODDOCS|Module Docs", "pythonw.exe",
   1283                tcltk.id, r'"[TARGETDIR]Tools\scripts\pydocgui.pyw"', None, None, "python_icon.exe", 0, None, "TARGETDIR"),
   1284               ]
   1285     add_data(db, "Shortcut",
   1286              tcltkshortcuts +
   1287              [# Advertised shortcuts: targets are features, not files

   1288               ("Python", "MenuDir", "PYTHON|Python (command line)", "python.exe",
   1289                default_feature.id, None, None, None, "python_icon.exe", 2, None, "TARGETDIR"),
   1290               # Advertising the Manual breaks on (some?) Win98, and the shortcut lacks an

   1291               # icon first.

   1292               #("Manual", "MenuDir", "MANUAL|Python Manuals", "documentation",

   1293               # htmlfiles.id, None, None, None, None, None, None, None),

   1294               ## Non-advertised shortcuts: must be associated with a registry component

   1295               ("Manual", "MenuDir", "MANUAL|Python Manuals", "REGISTRY.doc",
   1296                "[#%s]" % docfile, None,
   1297                None, None, None, None, None, None),
   1298               ("Uninstall", "MenuDir", "UNINST|Uninstall Python", "REGISTRY",
   1299                SystemFolderName+"msiexec",  "/x%s" % product_code,
   1300                None, None, None, None, None, None),
   1301               ])
   1302     db.Commit()
   1303 
   1304 def build_pdbzip():
   1305     pdbexclude = ['kill_python.pdb', 'make_buildinfo.pdb',
   1306                   'make_versioninfo.pdb']
   1307     path = "python-%s%s-pdb.zip" % (full_current_version, msilib.arch_ext)
   1308     pdbzip = zipfile.ZipFile(path, 'w')
   1309     for f in glob.glob1(os.path.join(srcdir, PCBUILD), "*.pdb"):
   1310         if f not in pdbexclude and not f.endswith('_d.pdb'):
   1311             pdbzip.write(os.path.join(srcdir, PCBUILD, f), f)
   1312     pdbzip.close()
   1313 
   1314 db,msiname = build_database()
   1315 try:
   1316     add_features(db)
   1317     add_ui(db)
   1318     add_files(db)
   1319     add_registry(db)
   1320     remove_old_versions(db)
   1321     db.Commit()
   1322 finally:
   1323     del db
   1324 
   1325 # Merge CRT into MSI file. This requires the database to be closed.

   1326 mod_dir = os.path.join(os.environ["ProgramFiles"], "Common Files", "Merge Modules")
   1327 if msilib.Win64:
   1328     modules = ["Microsoft_VC90_CRT_x86_x64.msm", "policy_9_0_Microsoft_VC90_CRT_x86_x64.msm"]
   1329 else:
   1330     modules = ["Microsoft_VC90_CRT_x86.msm","policy_9_0_Microsoft_VC90_CRT_x86.msm"]
   1331 
   1332 for i, n in enumerate(modules):
   1333     modules[i] = os.path.join(mod_dir, n)
   1334 
   1335 def merge(msi, feature, rootdir, modules):
   1336     cab_and_filecount = []
   1337     # Step 1: Merge databases, extract cabfiles

   1338     m = msilib.MakeMerge2()
   1339     m.OpenLog("merge.log")
   1340     m.OpenDatabase(msi)
   1341     for module in modules:
   1342         print module
   1343         m.OpenModule(module,0)
   1344         m.Merge(feature, rootdir)
   1345         print "Errors:"
   1346         for e in m.Errors:
   1347             print e.Type, e.ModuleTable, e.DatabaseTable
   1348             print "   Modkeys:",
   1349             for s in e.ModuleKeys: print s,
   1350             print
   1351             print "   DBKeys:",
   1352             for s in e.DatabaseKeys: print s,
   1353             print
   1354         cabname = tempfile.mktemp(suffix=".cab")
   1355         m.ExtractCAB(cabname)
   1356         cab_and_filecount.append((cabname, len(m.ModuleFiles)))
   1357         m.CloseModule()
   1358     m.CloseDatabase(True)
   1359     m.CloseLog()
   1360 
   1361     # Step 2: Add CAB files

   1362     i = msilib.MakeInstaller()
   1363     db = i.OpenDatabase(msi, constants.msiOpenDatabaseModeTransact)
   1364 
   1365     v = db.OpenView("SELECT LastSequence FROM Media")
   1366     v.Execute(None)
   1367     maxmedia = -1
   1368     while 1:
   1369         r = v.Fetch()
   1370         if not r: break
   1371         seq = r.IntegerData(1)
   1372         if seq > maxmedia:
   1373             maxmedia = seq
   1374     print "Start of Media", maxmedia
   1375 
   1376     for cabname, count in cab_and_filecount:
   1377         stream = "merged%d" % maxmedia
   1378         msilib.add_data(db, "Media",
   1379                 [(maxmedia+1, maxmedia+count, None, "#"+stream, None, None)])
   1380         msilib.add_stream(db, stream,  cabname)
   1381         os.unlink(cabname)
   1382         maxmedia += count
   1383     # The merge module sets ALLUSERS to 1 in the property table.

   1384     # This is undesired; delete that

   1385     v = db.OpenView("DELETE FROM Property WHERE Property='ALLUSERS'")
   1386     v.Execute(None)
   1387     v.Close()
   1388     db.Commit()
   1389 
   1390 merge(msiname, "SharedCRT", "TARGETDIR", modules)
   1391 
   1392 # certname (from config.py) should be (a substring of)

   1393 # the certificate subject, e.g. "Python Software Foundation"

   1394 if certname:
   1395     os.system('signtool sign /n "%s" /t http://timestamp.verisign.com/scripts/timestamp.dll %s' % (certname, msiname))
   1396 
   1397 if pdbzip:
   1398     build_pdbzip()
   1399