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