PageRenderTime 968ms CodeModel.GetById 159ms app.highlight 582ms RepoModel.GetById 114ms app.codeStats 1ms

/Tools/msi/msi.py

http://unladen-swallow.googlecode.com/
Python | 1301 lines | 1258 code | 11 blank | 32 comment | 57 complexity | c42c3665caff481aaffbdac3b7a308c5 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1# Python MSI Generator
   2# (C) 2003 Martin v. Loewis
   3# See "FOO" in comments refers to MSDN sections with the title FOO.
   4import msilib, schema, sequence, os, glob, time, re, shutil
   5from msilib import Feature, CAB, Directory, Dialog, Binary, add_data
   6import uisample
   7from win32com.client import constants
   8from distutils.spawn import find_executable
   9from uuids import product_codes
  10
  11# Settings can be overridden in config.py below
  12# 0 for official python.org releases
  13# 1 for intermediate releases by anybody, with
  14# a new product code for every package.
  15snapshot = 1
  16# 1 means that file extension is px, not py,
  17# and binaries start with x
  18testpackage = 0
  19# Location of build tree
  20srcdir = os.path.abspath("../..")
  21# Text to be displayed as the version in dialogs etc.
  22# goes into file name and ProductCode. Defaults to
  23# current_version.day for Snapshot, current_version otherwise
  24full_current_version = None
  25# Is Tcl available at all?
  26have_tcl = True
  27# path to PCbuild directory
  28PCBUILD="PCbuild"
  29# msvcrt version
  30MSVCR = "90"
  31
  32try:
  33    from config import *
  34except ImportError:
  35    pass
  36
  37# Extract current version from Include/patchlevel.h
  38lines = open(srcdir + "/Include/patchlevel.h").readlines()
  39major = minor = micro = level = serial = None
  40levels = {
  41    'PY_RELEASE_LEVEL_ALPHA':0xA,
  42    'PY_RELEASE_LEVEL_BETA': 0xB,
  43    'PY_RELEASE_LEVEL_GAMMA':0xC,
  44    'PY_RELEASE_LEVEL_FINAL':0xF
  45    }
  46for l in lines:
  47    if not l.startswith("#define"):
  48        continue
  49    l = l.split()
  50    if len(l) != 3:
  51        continue
  52    _, name, value = l
  53    if name == 'PY_MAJOR_VERSION': major = value
  54    if name == 'PY_MINOR_VERSION': minor = value
  55    if name == 'PY_MICRO_VERSION': micro = value
  56    if name == 'PY_RELEASE_LEVEL': level = levels[value]
  57    if name == 'PY_RELEASE_SERIAL': serial = value
  58
  59short_version = major+"."+minor
  60# See PC/make_versioninfo.c
  61FIELD3 = 1000*int(micro) + 10*level + int(serial)
  62current_version = "%s.%d" % (short_version, FIELD3)
  63
  64# This should never change. The UpgradeCode of this package can be
  65# used in the Upgrade table of future packages to make the future
  66# package replace this one. See "UpgradeCode Property".
  67# upgrade_code gets set to upgrade_code_64 when we have determined
  68# that the target is Win64.
  69upgrade_code_snapshot='{92A24481-3ECB-40FC-8836-04B7966EC0D5}'
  70upgrade_code='{65E6DE48-A358-434D-AA4F-4AF72DB4718F}'
  71upgrade_code_64='{6A965A0C-6EE6-4E3A-9983-3263F56311EC}'
  72
  73if snapshot:
  74    current_version = "%s.%s.%s" % (major, minor, int(time.time()/3600/24))
  75    product_code = msilib.gen_uuid()
  76else:
  77    product_code = product_codes[current_version]
  78
  79if full_current_version is None:
  80    full_current_version = current_version
  81
  82extensions = [
  83    'bz2.pyd',
  84    'pyexpat.pyd',
  85    'select.pyd',
  86    'unicodedata.pyd',
  87    'winsound.pyd',
  88    '_elementtree.pyd',
  89    '_bsddb.pyd',
  90    '_socket.pyd',
  91    '_ssl.pyd',
  92    '_testcapi.pyd',
  93    '_tkinter.pyd',
  94    '_msi.pyd',
  95    '_ctypes.pyd',
  96    '_ctypes_test.pyd',
  97    '_sqlite3.pyd',
  98    '_hashlib.pyd',
  99    '_multiprocessing.pyd'
 100]
 101
 102# Well-known component UUIDs
 103# These are needed for SharedDLLs reference counter; if
 104# a different UUID was used for each incarnation of, say,
 105# python24.dll, an upgrade would set the reference counter
 106# from 1 to 2 (due to what I consider a bug in MSI)
 107# Using the same UUID is fine since these files are versioned,
 108# so Installer will always keep the newest version.
 109# NOTE: All uuids are self generated.
 110pythondll_uuid = {
 111    "24":"{9B81E618-2301-4035-AC77-75D9ABEB7301}",
 112    "25":"{2e41b118-38bd-4c1b-a840-6977efd1b911}",
 113    "26":"{34ebecac-f046-4e1c-b0e3-9bac3cdaacfa}",
 114    } [major+minor]
 115
 116# Compute the name that Sphinx gives to the docfile
 117docfile = ""
 118if micro:
 119    docfile = str(micro)
 120if level < 0xf:
 121    docfile += '%x%s' % (level, serial)
 122docfile = 'python%s%s%s.chm' % (major, minor, docfile)
 123
 124# Build the mingw import library, libpythonXY.a
 125# This requires 'nm' and 'dlltool' executables on your PATH
 126def build_mingw_lib(lib_file, def_file, dll_file, mingw_lib):
 127    warning = "WARNING: %s - libpythonXX.a not built"
 128    nm = find_executable('nm')
 129    dlltool = find_executable('dlltool')
 130
 131    if not nm or not dlltool:
 132        print warning % "nm and/or dlltool were not found"
 133        return False
 134
 135    nm_command = '%s -Cs %s' % (nm, lib_file)
 136    dlltool_command = "%s --dllname %s --def %s --output-lib %s" % \
 137        (dlltool, dll_file, def_file, mingw_lib)
 138    export_match = re.compile(r"^_imp__(.*) in python\d+\.dll").match
 139
 140    f = open(def_file,'w')
 141    print >>f, "LIBRARY %s" % dll_file
 142    print >>f, "EXPORTS"
 143
 144    nm_pipe = os.popen(nm_command)
 145    for line in nm_pipe.readlines():
 146        m = export_match(line)
 147        if m:
 148            print >>f, m.group(1)
 149    f.close()
 150    exit = nm_pipe.close()
 151
 152    if exit:
 153        print warning % "nm did not run successfully"
 154        return False
 155
 156    if os.system(dlltool_command) != 0:
 157        print warning % "dlltool did not run successfully"
 158        return False
 159
 160    return True
 161
 162# Target files (.def and .a) go in PCBuild directory
 163lib_file = os.path.join(srcdir, PCBUILD, "python%s%s.lib" % (major, minor))
 164def_file = os.path.join(srcdir, PCBUILD, "python%s%s.def" % (major, minor))
 165dll_file = "python%s%s.dll" % (major, minor)
 166mingw_lib = os.path.join(srcdir, PCBUILD, "libpython%s%s.a" % (major, minor))
 167
 168have_mingw = build_mingw_lib(lib_file, def_file, dll_file, mingw_lib)
 169
 170# Determine the target architechture
 171dll_path = os.path.join(srcdir, PCBUILD, dll_file)
 172msilib.set_arch_from_file(dll_path)
 173if msilib.pe_type(dll_path) != msilib.pe_type("msisupport.dll"):
 174    raise SystemError, "msisupport.dll for incorrect architecture"
 175if msilib.Win64:
 176    upgrade_code = upgrade_code_64
 177    # Bump the last digit of the code by one, so that 32-bit and 64-bit
 178    # releases get separate product codes
 179    digit = hex((int(product_code[-2],16)+1)%16)[-1]
 180    product_code = product_code[:-2] + digit + '}'
 181
 182if testpackage:
 183    ext = 'px'
 184    testprefix = 'x'
 185else:
 186    ext = 'py'
 187    testprefix = ''
 188
 189if msilib.Win64:
 190    SystemFolderName = "[System64Folder]"
 191    registry_component = 4|256
 192else:
 193    SystemFolderName = "[SystemFolder]"
 194    registry_component = 4
 195
 196msilib.reset()
 197
 198# condition in which to install pythonxy.dll in system32:
 199# a) it is Windows 9x or
 200# b) it is NT, the user is privileged, and has chosen per-machine installation
 201sys32cond = "(Windows9x or (Privileged and ALLUSERS))"
 202
 203def build_database():
 204    """Generate an empty database, with just the schema and the
 205    Summary information stream."""
 206    if snapshot:
 207        uc = upgrade_code_snapshot
 208    else:
 209        uc = upgrade_code
 210    if msilib.Win64:
 211        productsuffix = " (64-bit)"
 212    else:
 213        productsuffix = ""
 214    # schema represents the installer 2.0 database schema.
 215    # sequence is the set of standard sequences
 216    # (ui/execute, admin/advt/install)
 217    db = msilib.init_database("python-%s%s.msi" % (full_current_version, msilib.arch_ext),
 218                  schema, ProductName="Python "+full_current_version+productsuffix,
 219                  ProductCode=product_code,
 220                  ProductVersion=current_version,
 221                  Manufacturer=u"Python Software Foundation",
 222                  request_uac = True)
 223    # The default sequencing of the RemoveExistingProducts action causes
 224    # removal of files that got just installed. Place it after
 225    # InstallInitialize, so we first uninstall everything, but still roll
 226    # back in case the installation is interrupted
 227    msilib.change_sequence(sequence.InstallExecuteSequence,
 228                           "RemoveExistingProducts", 1510)
 229    msilib.add_tables(db, sequence)
 230    # We cannot set ALLUSERS in the property table, as this cannot be
 231    # reset if the user choses a per-user installation. Instead, we
 232    # maintain WhichUsers, which can be "ALL" or "JUSTME". The UI manages
 233    # this property, and when the execution starts, ALLUSERS is set
 234    # accordingly.
 235    add_data(db, "Property", [("UpgradeCode", uc),
 236                              ("WhichUsers", "ALL"),
 237                              ("ProductLine", "Python%s%s" % (major, minor)),
 238                             ])
 239    db.Commit()
 240    return db
 241
 242def remove_old_versions(db):
 243    "Fill the upgrade table."
 244    start = "%s.%s.0" % (major, minor)
 245    # This requests that feature selection states of an older
 246    # installation should be forwarded into this one. Upgrading
 247    # requires that both the old and the new installation are
 248    # either both per-machine or per-user.
 249    migrate_features = 1
 250    # See "Upgrade Table". We remove releases with the same major and
 251    # minor version. For an snapshot, we remove all earlier snapshots. For
 252    # a release, we remove all snapshots, and all earlier releases.
 253    if snapshot:
 254        add_data(db, "Upgrade",
 255            [(upgrade_code_snapshot, start,
 256              current_version,
 257              None,                     # Ignore language
 258              migrate_features,
 259              None,                     # Migrate ALL features
 260              "REMOVEOLDSNAPSHOT")])
 261        props = "REMOVEOLDSNAPSHOT"
 262    else:
 263        add_data(db, "Upgrade",
 264            [(upgrade_code, start, current_version,
 265              None, migrate_features, None, "REMOVEOLDVERSION"),
 266             (upgrade_code_snapshot, start, "%s.%d.0" % (major, int(minor)+1),
 267              None, migrate_features, None, "REMOVEOLDSNAPSHOT")])
 268        props = "REMOVEOLDSNAPSHOT;REMOVEOLDVERSION"
 269
 270    props += ";TARGETDIR;DLLDIR"
 271    # Installer collects the product codes of the earlier releases in
 272    # these properties. In order to allow modification of the properties,
 273    # they must be declared as secure. See "SecureCustomProperties Property"
 274    add_data(db, "Property", [("SecureCustomProperties", props)])
 275
 276class PyDialog(Dialog):
 277    """Dialog class with a fixed layout: controls at the top, then a ruler,
 278    then a list of buttons: back, next, cancel. Optionally a bitmap at the
 279    left."""
 280    def __init__(self, *args, **kw):
 281        """Dialog(database, name, x, y, w, h, attributes, title, first,
 282        default, cancel, bitmap=true)"""
 283        Dialog.__init__(self, *args)
 284        ruler = self.h - 36
 285        bmwidth = 152*ruler/328
 286        if kw.get("bitmap", True):
 287            self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin")
 288        self.line("BottomLine", 0, ruler, self.w, 0)
 289
 290    def title(self, title):
 291        "Set the title text of the dialog at the top."
 292        # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix,
 293        # text, in VerdanaBold10
 294        self.text("Title", 135, 10, 220, 60, 0x30003,
 295                  r"{\VerdanaBold10}%s" % title)
 296
 297    def back(self, title, next, name = "Back", active = 1):
 298        """Add a back button with a given title, the tab-next button,
 299        its name in the Control table, possibly initially disabled.
 300
 301        Return the button, so that events can be associated"""
 302        if active:
 303            flags = 3 # Visible|Enabled
 304        else:
 305            flags = 1 # Visible
 306        return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next)
 307
 308    def cancel(self, title, next, name = "Cancel", active = 1):
 309        """Add a cancel 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, 304, self.h-27, 56, 17, flags, title, next)
 318
 319    def next(self, title, next, name = "Next", active = 1):
 320        """Add a Next 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, 236, self.h-27, 56, 17, flags, title, next)
 329
 330    def xbutton(self, name, title, next, xpos):
 331        """Add a button with a given title, the tab-next button,
 332        its name in the Control table, giving its x position; the
 333        y-position is aligned with the other buttons.
 334
 335        Return the button, so that events can be associated"""
 336        return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next)
 337
 338def add_ui(db):
 339    x = y = 50
 340    w = 370
 341    h = 300
 342    title = "[ProductName] Setup"
 343
 344    # see "Dialog Style Bits"
 345    modal = 3      # visible | modal
 346    modeless = 1   # visible
 347    track_disk_space = 32
 348
 349    add_data(db, 'ActionText', uisample.ActionText)
 350    add_data(db, 'UIText', uisample.UIText)
 351
 352    # Bitmaps
 353    if not os.path.exists(srcdir+r"\PC\python_icon.exe"):
 354        raise "Run icons.mak in PC directory"
 355    add_data(db, "Binary",
 356             [("PythonWin", msilib.Binary(r"%s\PCbuild\installer.bmp" % srcdir)), # 152x328 pixels
 357              ("py.ico",msilib.Binary(srcdir+r"\PC\py.ico")),
 358             ])
 359    add_data(db, "Icon",
 360             [("python_icon.exe", msilib.Binary(srcdir+r"\PC\python_icon.exe"))])
 361
 362    # Scripts
 363    # CheckDir sets TargetExists if TARGETDIR exists.
 364    # UpdateEditIDLE sets the REGISTRY.tcl component into
 365    # the installed/uninstalled state according to both the
 366    # Extensions and TclTk features.
 367    if os.system("nmake /nologo /c /f msisupport.mak") != 0:
 368        raise "'nmake /f msisupport.mak' failed"
 369    add_data(db, "Binary", [("Script", msilib.Binary("msisupport.dll"))])
 370    # See "Custom Action Type 1"
 371    if msilib.Win64:
 372        CheckDir = "CheckDir"
 373        UpdateEditIDLE = "UpdateEditIDLE"
 374    else:
 375        CheckDir =  "_CheckDir@4"
 376        UpdateEditIDLE = "_UpdateEditIDLE@4"
 377    add_data(db, "CustomAction",
 378        [("CheckDir", 1, "Script", CheckDir)])
 379    if have_tcl:
 380        add_data(db, "CustomAction",
 381        [("UpdateEditIDLE", 1, "Script", UpdateEditIDLE)])
 382
 383    # UI customization properties
 384    add_data(db, "Property",
 385             # See "DefaultUIFont Property"
 386             [("DefaultUIFont", "DlgFont8"),
 387              # See "ErrorDialog Style Bit"
 388              ("ErrorDialog", "ErrorDlg"),
 389              ("Progress1", "Install"),   # modified in maintenance type dlg
 390              ("Progress2", "installs"),
 391              ("MaintenanceForm_Action", "Repair")])
 392
 393    # Fonts, see "TextStyle Table"
 394    add_data(db, "TextStyle",
 395             [("DlgFont8", "Tahoma", 9, None, 0),
 396              ("DlgFontBold8", "Tahoma", 8, None, 1), #bold
 397              ("VerdanaBold10", "Verdana", 10, None, 1),
 398              ("VerdanaRed9", "Verdana", 9, 255, 0),
 399             ])
 400
 401    compileargs = r'-Wi "[TARGETDIR]Lib\compileall.py" -f -x bad_coding|badsyntax|site-packages|py3_ "[TARGETDIR]Lib"'
 402    lib2to3args = r'-c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"'
 403    # See "CustomAction Table"
 404    add_data(db, "CustomAction", [
 405        # msidbCustomActionTypeFirstSequence + msidbCustomActionTypeTextData + msidbCustomActionTypeProperty
 406        # See "Custom Action Type 51",
 407        # "Custom Action Execution Scheduling Options"
 408        ("InitialTargetDir", 307, "TARGETDIR",
 409         "[WindowsVolume]Python%s%s" % (major, minor)),
 410        ("SetDLLDirToTarget", 307, "DLLDIR", "[TARGETDIR]"),
 411        ("SetDLLDirToSystem32", 307, "DLLDIR", SystemFolderName),
 412        # msidbCustomActionTypeExe + msidbCustomActionTypeSourceFile
 413        # See "Custom Action Type 18"
 414        ("CompilePyc", 18, "python.exe", compileargs),
 415        ("CompilePyo", 18, "python.exe", "-O "+compileargs),
 416        ("CompileGrammar", 18, "python.exe", lib2to3args),
 417        ])
 418
 419    # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table"
 420    # Numbers indicate sequence; see sequence.py for how these action integrate
 421    add_data(db, "InstallUISequence",
 422             [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140),
 423              ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141),
 424              ("InitialTargetDir", 'TARGETDIR=""', 750),
 425              # In the user interface, assume all-users installation if privileged.
 426              ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751),
 427              ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752),
 428              ("SelectDirectoryDlg", "Not Installed", 1230),
 429              # XXX no support for resume installations yet
 430              #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240),
 431              ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250),
 432              ("ProgressDlg", None, 1280)])
 433    add_data(db, "AdminUISequence",
 434             [("InitialTargetDir", 'TARGETDIR=""', 750),
 435              ("SetDLLDirToTarget", 'DLLDIR=""', 751),
 436             ])
 437
 438    # Execute Sequences
 439    add_data(db, "InstallExecuteSequence",
 440            [("InitialTargetDir", 'TARGETDIR=""', 750),
 441             ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751),
 442             ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752),
 443             ("UpdateEditIDLE", None, 1050),
 444             ("CompilePyc", "COMPILEALL", 6800),
 445             ("CompilePyo", "COMPILEALL", 6801),
 446             ("CompileGrammar", "COMPILEALL", 6802),
 447            ])
 448    add_data(db, "AdminExecuteSequence",
 449            [("InitialTargetDir", 'TARGETDIR=""', 750),
 450             ("SetDLLDirToTarget", 'DLLDIR=""', 751),
 451             ("CompilePyc", "COMPILEALL", 6800),
 452             ("CompilePyo", "COMPILEALL", 6801),
 453             ("CompileGrammar", "COMPILEALL", 6802),
 454            ])
 455
 456    #####################################################################
 457    # Standard dialogs: FatalError, UserExit, ExitDialog
 458    fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title,
 459                 "Finish", "Finish", "Finish")
 460    fatal.title("[ProductName] Installer ended prematurely")
 461    fatal.back("< Back", "Finish", active = 0)
 462    fatal.cancel("Cancel", "Back", active = 0)
 463    fatal.text("Description1", 135, 70, 220, 80, 0x30003,
 464               "[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.")
 465    fatal.text("Description2", 135, 155, 220, 20, 0x30003,
 466               "Click the Finish button to exit the Installer.")
 467    c=fatal.next("Finish", "Cancel", name="Finish")
 468    # See "ControlEvent Table". Parameters are the event, the parameter
 469    # to the action, and optionally the condition for the event, and the order
 470    # of events.
 471    c.event("EndDialog", "Exit")
 472
 473    user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title,
 474                 "Finish", "Finish", "Finish")
 475    user_exit.title("[ProductName] Installer was interrupted")
 476    user_exit.back("< Back", "Finish", active = 0)
 477    user_exit.cancel("Cancel", "Back", active = 0)
 478    user_exit.text("Description1", 135, 70, 220, 80, 0x30003,
 479               "[ProductName] setup was interrupted.  Your system has not been modified.  "
 480               "To install this program at a later time, please run the installation again.")
 481    user_exit.text("Description2", 135, 155, 220, 20, 0x30003,
 482               "Click the Finish button to exit the Installer.")
 483    c = user_exit.next("Finish", "Cancel", name="Finish")
 484    c.event("EndDialog", "Exit")
 485
 486    exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title,
 487                         "Finish", "Finish", "Finish")
 488    exit_dialog.title("Completing the [ProductName] Installer")
 489    exit_dialog.back("< Back", "Finish", active = 0)
 490    exit_dialog.cancel("Cancel", "Back", active = 0)
 491    exit_dialog.text("Acknowledgements", 135, 95, 220, 120, 0x30003,
 492      "Special Windows thanks to:\n"
 493      "    Mark Hammond, without whose years of freely \n"
 494      "    shared Windows expertise, Python for Windows \n"
 495      "    would still be Python for DOS.")
 496
 497    c = exit_dialog.text("warning", 135, 200, 220, 40, 0x30003,
 498            "{\\VerdanaRed9}Warning: Python 2.5.x is the last "
 499            "Python release for Windows 9x.")
 500    c.condition("Hide", "NOT Version9X")
 501
 502    exit_dialog.text("Description", 135, 235, 220, 20, 0x30003,
 503               "Click the Finish button to exit the Installer.")
 504    c = exit_dialog.next("Finish", "Cancel", name="Finish")
 505    c.event("EndDialog", "Return")
 506
 507    #####################################################################
 508    # Required dialog: FilesInUse, ErrorDlg
 509    inuse = PyDialog(db, "FilesInUse",
 510                     x, y, w, h,
 511                     19,                # KeepModeless|Modal|Visible
 512                     title,
 513                     "Retry", "Retry", "Retry", bitmap=False)
 514    inuse.text("Title", 15, 6, 200, 15, 0x30003,
 515               r"{\DlgFontBold8}Files in Use")
 516    inuse.text("Description", 20, 23, 280, 20, 0x30003,
 517               "Some files that need to be updated are currently in use.")
 518    inuse.text("Text", 20, 55, 330, 50, 3,
 519               "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.")
 520    inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess",
 521                  None, None, None)
 522    c=inuse.back("Exit", "Ignore", name="Exit")
 523    c.event("EndDialog", "Exit")
 524    c=inuse.next("Ignore", "Retry", name="Ignore")
 525    c.event("EndDialog", "Ignore")
 526    c=inuse.cancel("Retry", "Exit", name="Retry")
 527    c.event("EndDialog","Retry")
 528
 529
 530    # See "Error Dialog". See "ICE20" for the required names of the controls.
 531    error = Dialog(db, "ErrorDlg",
 532                   50, 10, 330, 101,
 533                   65543,       # Error|Minimize|Modal|Visible
 534                   title,
 535                   "ErrorText", None, None)
 536    error.text("ErrorText", 50,9,280,48,3, "")
 537    error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None)
 538    error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo")
 539    error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes")
 540    error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort")
 541    error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel")
 542    error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore")
 543    error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk")
 544    error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry")
 545
 546    #####################################################################
 547    # Global "Query Cancel" dialog
 548    cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title,
 549                    "No", "No", "No")
 550    cancel.text("Text", 48, 15, 194, 30, 3,
 551                "Are you sure you want to cancel [ProductName] installation?")
 552    cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
 553                   "py.ico", None, None)
 554    c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No")
 555    c.event("EndDialog", "Exit")
 556
 557    c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes")
 558    c.event("EndDialog", "Return")
 559
 560    #####################################################################
 561    # Global "Wait for costing" dialog
 562    costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title,
 563                     "Return", "Return", "Return")
 564    costing.text("Text", 48, 15, 194, 30, 3,
 565                 "Please wait while the installer finishes determining your disk space requirements.")
 566    costing.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
 567                    "py.ico", None, None)
 568    c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None)
 569    c.event("EndDialog", "Exit")
 570
 571    #####################################################################
 572    # Preparation dialog: no user input except cancellation
 573    prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title,
 574                    "Cancel", "Cancel", "Cancel")
 575    prep.text("Description", 135, 70, 220, 40, 0x30003,
 576              "Please wait while the Installer prepares to guide you through the installation.")
 577    prep.title("Welcome to the [ProductName] Installer")
 578    c=prep.text("ActionText", 135, 110, 220, 20, 0x30003, "Pondering...")
 579    c.mapping("ActionText", "Text")
 580    c=prep.text("ActionData", 135, 135, 220, 30, 0x30003, None)
 581    c.mapping("ActionData", "Text")
 582    prep.back("Back", None, active=0)
 583    prep.next("Next", None, active=0)
 584    c=prep.cancel("Cancel", None)
 585    c.event("SpawnDialog", "CancelDlg")
 586
 587    #####################################################################
 588    # Target directory selection
 589    seldlg = PyDialog(db, "SelectDirectoryDlg", x, y, w, h, modal, title,
 590                    "Next", "Next", "Cancel")
 591    seldlg.title("Select Destination Directory")
 592    c = seldlg.text("Existing", 135, 25, 235, 30, 0x30003,
 593                    "{\VerdanaRed9}This update will replace your existing [ProductLine] installation.")
 594    c.condition("Hide", 'REMOVEOLDVERSION="" and REMOVEOLDSNAPSHOT=""')
 595    seldlg.text("Description", 135, 50, 220, 40, 0x30003,
 596               "Please select a directory for the [ProductName] files.")
 597
 598    seldlg.back("< Back", None, active=0)
 599    c = seldlg.next("Next >", "Cancel")
 600    c.event("DoAction", "CheckDir", "TargetExistsOk<>1", order=1)
 601    # If the target exists, but we found that we are going to remove old versions, don't bother
 602    # confirming that the target directory exists. Strictly speaking, we should determine that
 603    # the target directory is indeed the target of the product that we are going to remove, but
 604    # I don't know how to do that.
 605    c.event("SpawnDialog", "ExistingDirectoryDlg", 'TargetExists=1 and REMOVEOLDVERSION="" and REMOVEOLDSNAPSHOT=""', 2)
 606    c.event("SetTargetPath", "TARGETDIR", 'TargetExists=0 or REMOVEOLDVERSION<>"" or REMOVEOLDSNAPSHOT<>""', 3)
 607    c.event("SpawnWaitDialog", "WaitForCostingDlg", "CostingComplete=1", 4)
 608    c.event("NewDialog", "SelectFeaturesDlg", 'TargetExists=0 or REMOVEOLDVERSION<>"" or REMOVEOLDSNAPSHOT<>""', 5)
 609
 610    c = seldlg.cancel("Cancel", "DirectoryCombo")
 611    c.event("SpawnDialog", "CancelDlg")
 612
 613    seldlg.control("DirectoryCombo", "DirectoryCombo", 135, 70, 172, 80, 393219,
 614                   "TARGETDIR", None, "DirectoryList", None)
 615    seldlg.control("DirectoryList", "DirectoryList", 135, 90, 208, 136, 3, "TARGETDIR",
 616                   None, "PathEdit", None)
 617    seldlg.control("PathEdit", "PathEdit", 135, 230, 206, 16, 3, "TARGETDIR", None, "Next", None)
 618    c = seldlg.pushbutton("Up", 306, 70, 18, 18, 3, "Up", None)
 619    c.event("DirectoryListUp", "0")
 620    c = seldlg.pushbutton("NewDir", 324, 70, 30, 18, 3, "New", None)
 621    c.event("DirectoryListNew", "0")
 622
 623    #####################################################################
 624    # SelectFeaturesDlg
 625    features = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal|track_disk_space,
 626                        title, "Tree", "Next", "Cancel")
 627    features.title("Customize [ProductName]")
 628    features.text("Description", 135, 35, 220, 15, 0x30003,
 629                  "Select the way you want features to be installed.")
 630    features.text("Text", 135,45,220,30, 3,
 631                  "Click on the icons in the tree below to change the way features will be installed.")
 632
 633    c=features.back("< Back", "Next")
 634    c.event("NewDialog", "SelectDirectoryDlg")
 635
 636    c=features.next("Next >", "Cancel")
 637    c.mapping("SelectionNoItems", "Enabled")
 638    c.event("SpawnDialog", "DiskCostDlg", "OutOfDiskSpace=1", order=1)
 639    c.event("EndDialog", "Return", "OutOfDiskSpace<>1", order=2)
 640
 641    c=features.cancel("Cancel", "Tree")
 642    c.event("SpawnDialog", "CancelDlg")
 643
 644    # The browse property is not used, since we have only a single target path (selected already)
 645    features.control("Tree", "SelectionTree", 135, 75, 220, 95, 7, "_BrowseProperty",
 646                     "Tree of selections", "Back", None)
 647
 648    #c=features.pushbutton("Reset", 42, 243, 56, 17, 3, "Reset", "DiskCost")
 649    #c.mapping("SelectionNoItems", "Enabled")
 650    #c.event("Reset", "0")
 651
 652    features.control("Box", "GroupBox", 135, 170, 225, 90, 1, None, None, None, None)
 653
 654    c=features.xbutton("DiskCost", "Disk &Usage", None, 0.10)
 655    c.mapping("SelectionNoItems","Enabled")
 656    c.event("SpawnDialog", "DiskCostDlg")
 657
 658    c=features.xbutton("Advanced", "Advanced", None, 0.30)
 659    c.event("SpawnDialog", "AdvancedDlg")
 660
 661    c=features.text("ItemDescription", 140, 180, 210, 30, 3,
 662                  "Multiline description of the currently selected item.")
 663    c.mapping("SelectionDescription","Text")
 664
 665    c=features.text("ItemSize", 140, 210, 210, 45, 3,
 666                    "The size of the currently selected item.")
 667    c.mapping("SelectionSize", "Text")
 668
 669    #####################################################################
 670    # Disk cost
 671    cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title,
 672                    "OK", "OK", "OK", bitmap=False)
 673    cost.text("Title", 15, 6, 200, 15, 0x30003,
 674              "{\DlgFontBold8}Disk Space Requirements")
 675    cost.text("Description", 20, 20, 280, 20, 0x30003,
 676              "The disk space required for the installation of the selected features.")
 677    cost.text("Text", 20, 53, 330, 60, 3,
 678              "The highlighted volumes (if any) do not have enough disk space "
 679              "available for the currently selected features.  You can either "
 680              "remove some files from the highlighted volumes, or choose to "
 681              "install less features onto local drive(s), or select different "
 682              "destination drive(s).")
 683    cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223,
 684                 None, "{120}{70}{70}{70}{70}", None, None)
 685    cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return")
 686
 687    #####################################################################
 688    # WhichUsers Dialog. Only available on NT, and for privileged users.
 689    # This must be run before FindRelatedProducts, because that will
 690    # take into account whether the previous installation was per-user
 691    # or per-machine. We currently don't support going back to this
 692    # dialog after "Next" was selected; to support this, we would need to
 693    # find how to reset the ALLUSERS property, and how to re-run
 694    # FindRelatedProducts.
 695    # On Windows9x, the ALLUSERS property is ignored on the command line
 696    # and in the Property table, but installer fails according to the documentation
 697    # if a dialog attempts to set ALLUSERS.
 698    whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title,
 699                        "AdminInstall", "Next", "Cancel")
 700    whichusers.title("Select whether to install [ProductName] for all users of this computer.")
 701    # A radio group with two options: allusers, justme
 702    g = whichusers.radiogroup("AdminInstall", 135, 60, 235, 80, 3,
 703                              "WhichUsers", "", "Next")
 704    g.condition("Disable", "VersionNT=600") # Not available on Vista and Windows 2008
 705    g.add("ALL", 0, 5, 150, 20, "Install for all users")
 706    g.add("JUSTME", 0, 25, 235, 20, "Install just for me (not available on Windows Vista)")
 707
 708    whichusers.back("Back", None, active=0)
 709
 710    c = whichusers.next("Next >", "Cancel")
 711    c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1)
 712    c.event("EndDialog", "Return", order = 2)
 713
 714    c = whichusers.cancel("Cancel", "AdminInstall")
 715    c.event("SpawnDialog", "CancelDlg")
 716
 717    #####################################################################
 718    # Advanced Dialog.
 719    advanced = PyDialog(db, "AdvancedDlg", x, y, w, h, modal, title,
 720                        "CompilePyc", "Ok", "Ok")
 721    advanced.title("Advanced Options for [ProductName]")
 722    # A radio group with two options: allusers, justme
 723    advanced.checkbox("CompilePyc", 135, 60, 230, 50, 3,
 724                      "COMPILEALL", "Compile .py files to byte code after installation", "Ok")
 725
 726    c = advanced.cancel("Ok", "CompilePyc", name="Ok") # Button just has location of cancel button.
 727    c.event("EndDialog", "Return")
 728
 729    #####################################################################
 730    # Existing Directory dialog
 731    dlg = Dialog(db, "ExistingDirectoryDlg", 50, 30, 200, 80, modal, title,
 732                   "No", "No", "No")
 733    dlg.text("Title", 10, 20, 180, 40, 3,
 734             "[TARGETDIR] exists. Are you sure you want to overwrite existing files?")
 735    c=dlg.pushbutton("Yes", 30, 60, 55, 17, 3, "Yes", "No")
 736    c.event("[TargetExists]", "0", order=1)
 737    c.event("[TargetExistsOk]", "1", order=2)
 738    c.event("EndDialog", "Return", order=3)
 739    c=dlg.pushbutton("No", 115, 60, 55, 17, 3, "No", "Yes")
 740    c.event("EndDialog", "Return")
 741
 742    #####################################################################
 743    # Installation Progress dialog (modeless)
 744    progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title,
 745                        "Cancel", "Cancel", "Cancel", bitmap=False)
 746    progress.text("Title", 20, 15, 200, 15, 0x30003,
 747                  "{\DlgFontBold8}[Progress1] [ProductName]")
 748    progress.text("Text", 35, 65, 300, 30, 3,
 749                  "Please wait while the Installer [Progress2] [ProductName]. "
 750                  "This may take several minutes.")
 751    progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:")
 752
 753    c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...")
 754    c.mapping("ActionText", "Text")
 755
 756    #c=progress.text("ActionData", 35, 140, 300, 20, 3, None)
 757    #c.mapping("ActionData", "Text")
 758
 759    c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537,
 760                       None, "Progress done", None, None)
 761    c.mapping("SetProgress", "Progress")
 762
 763    progress.back("< Back", "Next", active=False)
 764    progress.next("Next >", "Cancel", active=False)
 765    progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg")
 766
 767    # Maintenance type: repair/uninstall
 768    maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title,
 769                     "Next", "Next", "Cancel")
 770    maint.title("Welcome to the [ProductName] Setup Wizard")
 771    maint.text("BodyText", 135, 63, 230, 42, 3,
 772               "Select whether you want to repair or remove [ProductName].")
 773    g=maint.radiogroup("RepairRadioGroup", 135, 108, 230, 60, 3,
 774                        "MaintenanceForm_Action", "", "Next")
 775    g.add("Change", 0, 0, 200, 17, "&Change [ProductName]")
 776    g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]")
 777    g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]")
 778
 779    maint.back("< Back", None, active=False)
 780    c=maint.next("Finish", "Cancel")
 781    # Change installation: Change progress dialog to "Change", then ask
 782    # for feature selection
 783    c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1)
 784    c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2)
 785
 786    # Reinstall: Change progress dialog to "Repair", then invoke reinstall
 787    # Also set list of reinstalled features to "ALL"
 788    c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5)
 789    c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6)
 790    c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7)
 791    c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8)
 792
 793    # Uninstall: Change progress to "Remove", then invoke uninstall
 794    # Also set list of removed features to "ALL"
 795    c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11)
 796    c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12)
 797    c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13)
 798    c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14)
 799
 800    # Close dialog when maintenance action scheduled
 801    c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20)
 802    c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21)
 803
 804    maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg")
 805
 806
 807# See "Feature Table". The feature level is 1 for all features,
 808# and the feature attributes are 0 for the DefaultFeature, and
 809# FollowParent for all other features. The numbers are the Display
 810# column.
 811def add_features(db):
 812    # feature attributes:
 813    # msidbFeatureAttributesFollowParent == 2
 814    # msidbFeatureAttributesDisallowAdvertise == 8
 815    # Features that need to be installed with together with the main feature
 816    # (i.e. additional Python libraries) need to follow the parent feature.
 817    # Features that have no advertisement trigger (e.g. the test suite)
 818    # must not support advertisement
 819    global default_feature, tcltk, htmlfiles, tools, testsuite, ext_feature, private_crt
 820    default_feature = Feature(db, "DefaultFeature", "Python",
 821                              "Python Interpreter and Libraries",
 822                              1, directory = "TARGETDIR")
 823    shared_crt = Feature(db, "SharedCRT", "MSVCRT", "C Run-Time (system-wide)", 0,
 824                         level=0)
 825    private_crt = Feature(db, "PrivateCRT", "MSVCRT", "C Run-Time (private)", 0,
 826                          level=0)
 827    add_data(db, "Condition", [("SharedCRT", 1, sys32cond),
 828                               ("PrivateCRT", 1, "not "+sys32cond)])
 829    # We don't support advertisement of extensions
 830    ext_feature = Feature(db, "Extensions", "Register Extensions",
 831                          "Make this Python installation the default Python installation", 3,
 832                         parent = default_feature, attributes=2|8)
 833    if have_tcl:
 834        tcltk = Feature(db, "TclTk", "Tcl/Tk", "Tkinter, IDLE, pydoc", 5,
 835                    parent = default_feature, attributes=2)
 836    htmlfiles = Feature(db, "Documentation", "Documentation",
 837                        "Python HTMLHelp File", 7, parent = default_feature)
 838    tools = Feature(db, "Tools", "Utility Scripts",
 839                    "Python utility scripts (Tools/", 9,
 840                    parent = default_feature, attributes=2)
 841    testsuite = Feature(db, "Testsuite", "Test suite",
 842                        "Python test suite (Lib/test/)", 11,
 843                        parent = default_feature, attributes=2|8)
 844
 845def extract_msvcr90():
 846    # Find the redistributable files
 847    if msilib.Win64:
 848        arch = "amd64"
 849    else:
 850        arch = "x86"
 851    dir = os.path.join(os.environ['VS90COMNTOOLS'], r"..\..\VC\redist\%s\Microsoft.VC90.CRT" % arch)
 852
 853    result = []
 854    installer = msilib.MakeInstaller()
 855    # omit msvcm90 and msvcp90, as they aren't really needed
 856    files = ["Microsoft.VC90.CRT.manifest", "msvcr90.dll"]
 857    for f in files:
 858        path = os.path.join(dir, f)
 859        kw = {'src':path}
 860        if f.endswith('.dll'):
 861            kw['version'] = installer.FileVersion(path, 0)
 862            kw['language'] = installer.FileVersion(path, 1)
 863        result.append((f, kw))
 864    return result
 865
 866def generate_license():
 867    import shutil, glob
 868    out = open("LICENSE.txt", "w")
 869    shutil.copyfileobj(open(os.path.join(srcdir, "LICENSE")), out)
 870    shutil.copyfileobj(open("crtlicense.txt"), out)
 871    for name, pat, file in (("bzip2","bzip2-*", "LICENSE"),
 872                      ("Berkeley DB", "db-*", "LICENSE"),
 873                      ("openssl", "openssl-*", "LICENSE"),
 874                      ("Tcl", "tcl8*", "license.terms"),
 875                      ("Tk", "tk8*", "license.terms"),
 876                      ("Tix", "tix-*", "license.terms")):
 877        out.write("\nThis copy of Python includes a copy of %s, which is licensed under the following terms:\n\n" % name)
 878        dirs = glob.glob(srcdir+"/../"+pat)
 879        if not dirs:
 880            raise ValueError, "Could not find "+srcdir+"/../"+pat
 881        if len(dirs) > 2:
 882            raise ValueError, "Multiple copies of "+pat
 883        dir = dirs[0]
 884        shutil.copyfileobj(open(os.path.join(dir, file)), out)
 885    out.close()
 886
 887
 888class PyDirectory(Directory):
 889    """By default, all components in the Python installer
 890    can run from source."""
 891    def __init__(self, *args, **kw):
 892        if not kw.has_key("componentflags"):
 893            kw['componentflags'] = 2 #msidbComponentAttributesOptional
 894        Directory.__init__(self, *args, **kw)
 895
 896# See "File Table", "Component Table", "Directory Table",
 897# "FeatureComponents Table"
 898def add_files(db):
 899    cab = CAB("python")
 900    tmpfiles = []
 901    # Add all executables, icons, text files into the TARGETDIR component
 902    root = PyDirectory(db, cab, None, srcdir, "TARGETDIR", "SourceDir")
 903    default_feature.set_current()
 904    if not msilib.Win64:
 905        root.add_file("%s/w9xpopen.exe" % PCBUILD)
 906    root.add_file("README.txt", src="README")
 907    root.add_file("NEWS.txt", src="Misc/NEWS")
 908    generate_license()
 909    root.add_file("LICENSE.txt", src=os.path.abspath("LICENSE.txt"))
 910    root.start_component("python.exe", keyfile="python.exe")
 911    root.add_file("%s/python.exe" % PCBUILD)
 912    root.start_component("pythonw.exe", keyfile="pythonw.exe")
 913    root.add_file("%s/pythonw.exe" % PCBUILD)
 914
 915    # msidbComponentAttributesSharedDllRefCount = 8, see "Component Table"
 916    dlldir = PyDirectory(db, cab, root, srcdir, "DLLDIR", ".")
 917
 918    pydll = "python%s%s.dll" % (major, minor)
 919    pydllsrc = os.path.join(srcdir, PCBUILD, pydll)
 920    dlldir.start_component("DLLDIR", flags = 8, keyfile = pydll, uuid = pythondll_uuid)
 921    installer = msilib.MakeInstaller()
 922    pyversion = installer.FileVersion(pydllsrc, 0)
 923    if not snapshot:
 924        # For releases, the Python DLL has the same version as the
 925        # installer package.
 926        assert pyversion.split(".")[:3] == current_version.split(".")
 927    dlldir.add_file("%s/python%s%s.dll" % (PCBUILD, major, minor),
 928                    version=pyversion,
 929                    language=installer.FileVersion(pydllsrc, 1))
 930    DLLs = PyDirectory(db, cab, root, srcdir + "/" + PCBUILD, "DLLs", "DLLS|DLLs")
 931
 932    # msvcr90.dll: Need to place the DLL and the manifest into the root directory,
 933    # plus another copy of the manifest in the DLLs directory, with the manifest
 934    # pointing to the root directory
 935    root.start_component("msvcr90", feature=private_crt)
 936    # Results are ID,keyword pairs
 937    manifest, crtdll = extract_msvcr90()
 938    root.add_file(manifest[0], **manifest[1])
 939    root.add_file(crtdll[0], **crtdll[1])
 940    # Copy the manifest
 941    # Actually, don't do that anymore - no DLL in DLLs should have a manifest
 942    # dependency on msvcr90.dll anymore, so this should not be necessary
 943    #manifest_dlls = manifest[0]+".root"
 944    #open(manifest_dlls, "w").write(open(manifest[1]['src']).read().replace("msvcr","../msvcr"))
 945    #DLLs.start_component("msvcr90_dlls", feature=private_crt)
 946    #DLLs.add_file(manifest[0], src=os.path.abspath(manifest_dlls))
 947
 948    # Now start the main component for the DLLs directory;
 949    # no regular files have been added to the directory yet.
 950    DLLs.start_component()
 951
 952    # Check if _ctypes.pyd exists
 953    have_ctypes = os.path.exists(srcdir+"/%s/_ctypes.pyd" % PCBUILD)
 954    if not have_ctypes:
 955        print "WARNING: _ctypes.pyd not found, ctypes will not be included"
 956        extensions.remove("_ctypes.pyd")
 957
 958    # Add all .py files in Lib, except lib-tk, test
 959    dirs={}
 960    pydirs = [(root,"Lib")]
 961    while pydirs:
 962        # Commit every now and then, or else installer will complain
 963        db.Commit()
 964        parent, dir = pydirs.pop()
 965        if dir == ".svn" or dir.startswith("plat-"):
 966            continue
 967        elif dir in ["lib-tk", "idlelib", "Icons"]:
 968            if not have_tcl:
 969                continue
 970            tcltk.set_current()
 971        elif dir in ['test', 'tests', 'data', 'output']:
 972            # test: Lib, Lib/email, Lib/bsddb, Lib/ctypes, Lib/sqlite3
 973            # tests: Lib/distutils
 974            # data: Lib/email/test
 975            # output: Lib/test
 976            testsuite.set_current()
 977        elif not have_ctypes and dir == "ctypes":
 978            continue
 979        else:
 980            default_feature.set_current()
 981        lib = PyDirectory(db, cab, parent, dir, dir, "%s|%s" % (parent.make_short(dir), dir))
 982        # Add additional files
 983        dirs[dir]=lib
 984        lib.glob("*.txt")
 985        if dir=='site-packages':
 986            lib.add_file("README.txt", src="README")
 987            continue
 988        files = lib.glob("*.py")
 989        files += lib.glob("*.pyw")
 990        if files:
 991            # Add an entry to the RemoveFile table to remove bytecode files.
 992            lib.remove_pyc()
 993        if dir.endswith('.egg-info'):
 994            lib.add_file('entry_points.txt')
 995            lib.add_file('PKG-INFO')
 996            lib.add_file('top_level.txt')
 997            lib.add_file('zip-safe')
 998            continue
 999        if dir=='test' and parent.physical=='Lib':
1000            lib.add_file("185test.db")
1001            lib.add_file("audiotest.au")
1002            lib.add_file("cfgparser.1")
1003            lib.add_file("sgml_input.html")
1004            lib.add_file("test.xml")
1005            lib.add_file("test.xml.out")
1006            lib.add_file("testtar.tar")
1007            lib.add_file("test_difflib_expect.html")
1008            lib.add_file("check_soundcard.vbs")
1009            lib.add_file("empty.vbs")
1010            lib.add_file("Sine-1000Hz-300ms.aif")
1011            lib.glob("*.uue")
1012            lib.glob("*.pem")
1013            lib.glob("*.pck")
1014            lib.add_file("readme.txt", src="README")
1015            lib.add_file("zipdir.zip")
1016        if dir=='decimaltestdata':
1017            lib.glob("*.decTest")
1018        if dir=='output':
1019            lib.glob("test_*")
1020        if dir=='idlelib':
1021            lib.glob("*.def")
1022            lib.add_file("idle.bat")
1023        if dir=="Icons":
1024            lib.glob("*.gif")
1025            lib.add_file("idle.icns")
1026        if dir=="command" and parent.physical=="distutils":
1027            lib.glob("wininst*.exe")
1028        if dir=="setuptools":
1029            lib.add_file("cli.exe")
1030            lib.add_file("gui.exe")
1031        if dir=="lib2to3":
1032            lib.removefile("pickle", "*.pickle")
1033        if dir=="data" and parent.physical=="test" and parent.basedir.physical=="email":
1034            # This should contain all non-.svn files listed in subversion
1035            for f in os.listdir(lib.absolute):
1036                if f.endswith(".txt") or f==".svn":continue
1037                if f.endswith(".au") or f.endswith(".gif"):
1038                    lib.add_file(f)
1039                else:
1040                    print "WARNING: New file %s in email/test/data" % f
1041        for f in os.listdir(lib.absolute):
1042            if os.path.isdir(os.path.join(lib.absolute, f)):
1043                pydirs.append((lib, f))
1044    # Add DLLs
1045    default_feature.set_current()
1046    lib = DLLs
1047    lib.add_file("py.ico", src=srcdir+"/PC/py.ico")
1048    lib.add_file("pyc.ico", src=srcdir+"/PC/pyc.ico")
1049    dlls = []
1050    tclfiles = []
1051    for f in extensions:
1052        if f=="_tkinter.pyd":
1053            continue
1054        if not os.path.exists(srcdir + "/" + PCBUILD + "/" + f):
1055            print "WARNING: Missing extension", f
1056            continue
1057        dlls.append(f)
1058        lib.add_file(f)
1059    # Add sqlite
1060    if msilib.msi_type=="Intel64;1033":
1061        sqlite_arch = "/ia64"
1062    elif msilib.msi_type=="x64;1033":
1063        sqlite_arch = "/amd64"
1064        tclsuffix = "64"
1065    else:
1066        sqlite_arch = ""
1067        tclsuffix = ""
1068    lib.add_file("sqlite3.dll")
1069    if have_tcl:
1070        if not os.path.exists("%s/%s/_tkinter.pyd" % (srcdir, PCBUILD)):
1071            print "WARNING: Missing _tkinter.pyd"
1072        else:
1073            lib.start_component("TkDLLs", tcltk)
1074            lib.add_file("_tkinter.pyd")
1075            dlls.append("_tkinter.pyd")
1076            tcldir = os.path.normpath(srcdir+("/../tcltk%s/bin" % tclsuffix))
1077            for f in glob.glob1(tcldir, "*.dll"):
1078                lib.add_file(f, src=os.path.join(tcldir, f))
1079    # check whether there are any unknown extensions
1080    for f in glob.glob1(srcdir+"/"+PCBUILD, "*.pyd"):
1081        if f.endswith("_d.pyd"): continue # debug version
1082        if f in dlls: continue
1083        print "WARNING: Unknown extension", f
1084
1085    # Add headers
1086    default_feature.set_current()
1087    lib = PyDirectory(db, cab, root, "include", "include", "INCLUDE|include")
1088    lib.glob("*.h")
1089    lib.add_file("pyconfig.h", src="../PC/pyconfig.h")
1090    # Add import libraries
1091    lib = PyDirectory(db, cab, root, PCBUILD, "libs", "LIBS|libs")
1092    for f in dlls:
1093        lib.add_file(f.replace('pyd','lib'))
1094    lib.add_file('python%s%s.lib' % (major, minor))
1095    # Add the mingw-format library
1096    if have_mingw:
1097        lib.add_file('libpython%s%s.a' % (major, minor))
1098    if have_tcl:
1099        # Add Tcl/Tk
1100        tcldirs = [(root, '../tcltk%s/lib' % tclsuffix, 'tcl')]
1101        tcltk.set_current()
1102        while tcldirs:
1103            parent, phys, dir = tcldirs.pop()
1104            lib = PyDirectory(db, cab, parent, phys, dir, "%s|%s" % (parent.make_short(dir), dir))
1105            if not os.path.exists(lib.absolute):
1106                continue
1107            for f in os.listdir(lib.absolute):
1108                if os.path.isdir(os.path.join(lib.absolute, f)):
1109                    tcldirs.append((lib, f, f))
1110                else:
1111                    lib.add_file(f)
1112    # Add tools
1113    tools.set_current()
1114    tooldir = PyDirectory(db, cab, root, "Tools", "Tools", "TOOLS|Tools")
1115    for f in ['i18n', 'pynche', 'Scripts', 'versioncheck', 'webchecker']:
1116        lib = PyDirectory(db, cab, tooldir, f, f, "%s|%s" % (tooldir.make_short(f), f))
1117        lib.glob("*.py")
1118        lib.glob("*.pyw", exclude=['pydocgui.pyw'])
1119        lib.remove_pyc()
1120        lib.glob("*.txt")
1121        if f == "pynche":
1122            x = PyDirectory(db, cab, lib, "X", "X", "X|X")
1123            x.glob("*.txt")
1124        if os.path.exists(os.path.join(lib.absolute, "README")):
1125            lib.add_file("README.txt", src="README")
1126        if f == 'Scripts':
1127            lib.add_file("2to3.py", src="2to3")
1128            if have_tcl:

Large files files are truncated, but you can click here to view the full file