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