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