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