»Core Development>Code coverage>Lib/packaging/command/bdist_msi.py

Python code coverage for Lib/packaging/command/bdist_msi.py

#countcontent
1n/a"""Create a Microsoft Installer (.msi) binary distribution."""
2n/a
3n/a# Copyright (C) 2005, 2006 Martin von Löwis
4n/a# Licensed to PSF under a Contributor Agreement.
5n/a
6n/aimport sys
7n/aimport os
8n/aimport msilib
9n/a
10n/afrom shutil import rmtree
11n/afrom sysconfig import get_python_version
12n/afrom packaging.command.cmd import Command
13n/afrom packaging.version import NormalizedVersion
14n/afrom packaging.errors import PackagingOptionError
15n/afrom packaging import logger as log
16n/afrom packaging.util import get_platform
17n/afrom msilib import schema, sequence, text
18n/afrom msilib import Directory, Feature, Dialog, add_data
19n/a
20n/aclass MSIVersion(NormalizedVersion):
21n/a """
22n/a MSI ProductVersion must be strictly numeric.
23n/a MSIVersion disallows prerelease and postrelease versions.
24n/a """
25n/a def __init__(self, *args, **kwargs):
26n/a super(MSIVersion, self).__init__(*args, **kwargs)
27n/a if not self.is_final:
28n/a raise ValueError("ProductVersion must be strictly numeric")
29n/a
30n/aclass PyDialog(Dialog):
31n/a """Dialog class with a fixed layout: controls at the top, then a ruler,
32n/a then a list of buttons: back, next, cancel. Optionally a bitmap at the
33n/a left."""
34n/a def __init__(self, *args, **kw):
35n/a """Dialog(database, name, x, y, w, h, attributes, title, first,
36n/a default, cancel, bitmap=true)"""
37n/a super(PyDialog, self).__init__(*args)
38n/a ruler = self.h - 36
39n/a #if kw.get("bitmap", True):
40n/a # self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin")
41n/a self.line("BottomLine", 0, ruler, self.w, 0)
42n/a
43n/a def title(self, title):
44n/a "Set the title text of the dialog at the top."
45n/a # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix,
46n/a # text, in VerdanaBold10
47n/a self.text("Title", 15, 10, 320, 60, 0x30003,
48n/a r"{\VerdanaBold10}%s" % title)
49n/a
50n/a def back(self, title, next, name = "Back", active = 1):
51n/a """Add a back button with a given title, the tab-next button,
52n/a its name in the Control table, possibly initially disabled.
53n/a
54n/a Return the button, so that events can be associated"""
55n/a if active:
56n/a flags = 3 # Visible|Enabled
57n/a else:
58n/a flags = 1 # Visible
59n/a return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next)
60n/a
61n/a def cancel(self, title, next, name = "Cancel", active = 1):
62n/a """Add a cancel button with a given title, the tab-next button,
63n/a its name in the Control table, possibly initially disabled.
64n/a
65n/a Return the button, so that events can be associated"""
66n/a if active:
67n/a flags = 3 # Visible|Enabled
68n/a else:
69n/a flags = 1 # Visible
70n/a return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next)
71n/a
72n/a def next(self, title, next, name = "Next", active = 1):
73n/a """Add a Next button with a given title, the tab-next button,
74n/a its name in the Control table, possibly initially disabled.
75n/a
76n/a Return the button, so that events can be associated"""
77n/a if active:
78n/a flags = 3 # Visible|Enabled
79n/a else:
80n/a flags = 1 # Visible
81n/a return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next)
82n/a
83n/a def xbutton(self, name, title, next, xpos):
84n/a """Add a button with a given title, the tab-next button,
85n/a its name in the Control table, giving its x position; the
86n/a y-position is aligned with the other buttons.
87n/a
88n/a Return the button, so that events can be associated"""
89n/a return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next)
90n/a
91n/aclass bdist_msi(Command):
92n/a
93n/a description = "create a Microsoft Installer (.msi) binary distribution"
94n/a
95n/a user_options = [('bdist-dir=', None,
96n/a "temporary directory for creating the distribution"),
97n/a ('plat-name=', 'p',
98n/a "platform name to embed in generated filenames "
99n/a "(default: %s)" % get_platform()),
100n/a ('keep-temp', 'k',
101n/a "keep the pseudo-installation tree around after " +
102n/a "creating the distribution archive"),
103n/a ('target-version=', None,
104n/a "require a specific python version" +
105n/a " on the target system"),
106n/a ('no-target-compile', 'c',
107n/a "do not compile .py to .pyc on the target system"),
108n/a ('no-target-optimize', 'o',
109n/a "do not compile .py to .pyo (optimized)"
110n/a "on the target system"),
111n/a ('dist-dir=', 'd',
112n/a "directory to put final built distributions in"),
113n/a ('skip-build', None,
114n/a "skip rebuilding everything (for testing/debugging)"),
115n/a ('install-script=', None,
116n/a "basename of installation script to be run after"
117n/a "installation or before deinstallation"),
118n/a ('pre-install-script=', None,
119n/a "Fully qualified filename of a script to be run before "
120n/a "any files are installed. This script need not be in the "
121n/a "distribution"),
122n/a ]
123n/a
124n/a boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
125n/a 'skip-build']
126n/a
127n/a all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4',
128n/a '2.5', '2.6', '2.7', '2.8', '2.9',
129n/a '3.0', '3.1', '3.2', '3.3', '3.4',
130n/a '3.5', '3.6', '3.7', '3.8', '3.9']
131n/a other_version = 'X'
132n/a
133n/a def initialize_options(self):
134n/a self.bdist_dir = None
135n/a self.plat_name = None
136n/a self.keep_temp = False
137n/a self.no_target_compile = False
138n/a self.no_target_optimize = False
139n/a self.target_version = None
140n/a self.dist_dir = None
141n/a self.skip_build = None
142n/a self.install_script = None
143n/a self.pre_install_script = None
144n/a self.versions = None
145n/a
146n/a def finalize_options(self):
147n/a self.set_undefined_options('bdist', 'skip_build')
148n/a
149n/a if self.bdist_dir is None:
150n/a bdist_base = self.get_finalized_command('bdist').bdist_base
151n/a self.bdist_dir = os.path.join(bdist_base, 'msi')
152n/a
153n/a short_version = get_python_version()
154n/a if (not self.target_version) and self.distribution.has_ext_modules():
155n/a self.target_version = short_version
156n/a
157n/a if self.target_version:
158n/a self.versions = [self.target_version]
159n/a if not self.skip_build and self.distribution.has_ext_modules()\
160n/a and self.target_version != short_version:
161n/a raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \
162n/a " option must be specified" % (short_version,))
163n/a else:
164n/a self.versions = list(self.all_versions)
165n/a
166n/a self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
167n/a
168n/a if self.pre_install_script:
169n/a raise PackagingOptionError("the pre-install-script feature is not yet implemented")
170n/a
171n/a if self.install_script:
172n/a for script in self.distribution.scripts:
173n/a if self.install_script == os.path.basename(script):
174n/a break
175n/a else:
176n/a raise PackagingOptionError("install_script '%s' not found in scripts" % \
177n/a self.install_script)
178n/a self.install_script_key = None
179n/a
180n/a
181n/a def run(self):
182n/a if not self.skip_build:
183n/a self.run_command('build')
184n/a
185n/a install = self.reinitialize_command('install_dist',
186n/a reinit_subcommands=True)
187n/a install.prefix = self.bdist_dir
188n/a install.skip_build = self.skip_build
189n/a install.warn_dir = False
190n/a
191n/a install_lib = self.reinitialize_command('install_lib')
192n/a # we do not want to include pyc or pyo files
193n/a install_lib.compile = False
194n/a install_lib.optimize = 0
195n/a
196n/a if self.distribution.has_ext_modules():
197n/a # If we are building an installer for a Python version other
198n/a # than the one we are currently running, then we need to ensure
199n/a # our build_lib reflects the other Python version rather than ours.
200n/a # Note that for target_version!=sys.version, we must have skipped the
201n/a # build step, so there is no issue with enforcing the build of this
202n/a # version.
203n/a target_version = self.target_version
204n/a if not target_version:
205n/a assert self.skip_build, "Should have already checked this"
206n/a target_version = '%s.%s' % sys.version_info[:2]
207n/a plat_specifier = ".%s-%s" % (self.plat_name, target_version)
208n/a build = self.get_finalized_command('build')
209n/a build.build_lib = os.path.join(build.build_base,
210n/a 'lib' + plat_specifier)
211n/a
212n/a log.info("installing to %s", self.bdist_dir)
213n/a install.ensure_finalized()
214n/a
215n/a # avoid warning of 'install_lib' about installing
216n/a # into a directory not in sys.path
217n/a sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
218n/a
219n/a install.run()
220n/a
221n/a del sys.path[0]
222n/a
223n/a self.mkpath(self.dist_dir)
224n/a fullname = self.distribution.get_fullname()
225n/a installer_name = self.get_installer_filename(fullname)
226n/a installer_name = os.path.abspath(installer_name)
227n/a if os.path.exists(installer_name): os.unlink(installer_name)
228n/a
229n/a metadata = self.distribution.metadata
230n/a author = metadata.author
231n/a if not author:
232n/a author = metadata.maintainer
233n/a if not author:
234n/a author = "UNKNOWN"
235n/a version = MSIVersion(metadata.get_version())
236n/a # Prefix ProductName with Python x.y, so that
237n/a # it sorts together with the other Python packages
238n/a # in Add-Remove-Programs (APR)
239n/a fullname = self.distribution.get_fullname()
240n/a if self.target_version:
241n/a product_name = "Python %s %s" % (self.target_version, fullname)
242n/a else:
243n/a product_name = "Python %s" % (fullname)
244n/a self.db = msilib.init_database(installer_name, schema,
245n/a product_name, msilib.gen_uuid(),
246n/a str(version), author)
247n/a msilib.add_tables(self.db, sequence)
248n/a props = [('DistVersion', version)]
249n/a email = metadata.author_email or metadata.maintainer_email
250n/a if email:
251n/a props.append(("ARPCONTACT", email))
252n/a if metadata.url:
253n/a props.append(("ARPURLINFOABOUT", metadata.url))
254n/a if props:
255n/a add_data(self.db, 'Property', props)
256n/a
257n/a self.add_find_python()
258n/a self.add_files()
259n/a self.add_scripts()
260n/a self.add_ui()
261n/a self.db.Commit()
262n/a
263n/a if hasattr(self.distribution, 'dist_files'):
264n/a tup = 'bdist_msi', self.target_version or 'any', fullname
265n/a self.distribution.dist_files.append(tup)
266n/a
267n/a if not self.keep_temp:
268n/a log.info("removing temporary build directory %s", self.bdist_dir)
269n/a if not self.dry_run:
270n/a rmtree(self.bdist_dir)
271n/a
272n/a def add_files(self):
273n/a db = self.db
274n/a cab = msilib.CAB("distfiles")
275n/a rootdir = os.path.abspath(self.bdist_dir)
276n/a
277n/a root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir")
278n/a f = Feature(db, "Python", "Python", "Everything",
279n/a 0, 1, directory="TARGETDIR")
280n/a
281n/a items = [(f, root, '')]
282n/a for version in self.versions + [self.other_version]:
283n/a target = "TARGETDIR" + version
284n/a name = default = "Python" + version
285n/a desc = "Everything"
286n/a if version is self.other_version:
287n/a title = "Python from another location"
288n/a level = 2
289n/a else:
290n/a title = "Python %s from registry" % version
291n/a level = 1
292n/a f = Feature(db, name, title, desc, 1, level, directory=target)
293n/a dir = Directory(db, cab, root, rootdir, target, default)
294n/a items.append((f, dir, version))
295n/a db.Commit()
296n/a
297n/a seen = {}
298n/a for feature, dir, version in items:
299n/a todo = [dir]
300n/a while todo:
301n/a dir = todo.pop()
302n/a for file in os.listdir(dir.absolute):
303n/a afile = os.path.join(dir.absolute, file)
304n/a if os.path.isdir(afile):
305n/a short = "%s|%s" % (dir.make_short(file), file)
306n/a default = file + version
307n/a newdir = Directory(db, cab, dir, file, default, short)
308n/a todo.append(newdir)
309n/a else:
310n/a if not dir.component:
311n/a dir.start_component(dir.logical, feature, 0)
312n/a if afile not in seen:
313n/a key = seen[afile] = dir.add_file(file)
314n/a if file==self.install_script:
315n/a if self.install_script_key:
316n/a raise PackagingOptionError(
317n/a "Multiple files with name %s" % file)
318n/a self.install_script_key = '[#%s]' % key
319n/a else:
320n/a key = seen[afile]
321n/a add_data(self.db, "DuplicateFile",
322n/a [(key + version, dir.component, key, None, dir.logical)])
323n/a db.Commit()
324n/a cab.commit(db)
325n/a
326n/a def add_find_python(self):
327n/a """Adds code to the installer to compute the location of Python.
328n/a
329n/a Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the
330n/a registry for each version of Python.
331n/a
332n/a Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined,
333n/a else from PYTHON.MACHINE.X.Y.
334n/a
335n/a Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe"""
336n/a
337n/a start = 402
338n/a for ver in self.versions:
339n/a install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver
340n/a machine_reg = "python.machine." + ver
341n/a user_reg = "python.user." + ver
342n/a machine_prop = "PYTHON.MACHINE." + ver
343n/a user_prop = "PYTHON.USER." + ver
344n/a machine_action = "PythonFromMachine" + ver
345n/a user_action = "PythonFromUser" + ver
346n/a exe_action = "PythonExe" + ver
347n/a target_dir_prop = "TARGETDIR" + ver
348n/a exe_prop = "PYTHON" + ver
349n/a if msilib.Win64:
350n/a # type: msidbLocatorTypeRawValue + msidbLocatorType64bit
351n/a Type = 2+16
352n/a else:
353n/a Type = 2
354n/a add_data(self.db, "RegLocator",
355n/a [(machine_reg, 2, install_path, None, Type),
356n/a (user_reg, 1, install_path, None, Type)])
357n/a add_data(self.db, "AppSearch",
358n/a [(machine_prop, machine_reg),
359n/a (user_prop, user_reg)])
360n/a add_data(self.db, "CustomAction",
361n/a [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"),
362n/a (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"),
363n/a (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"),
364n/a ])
365n/a add_data(self.db, "InstallExecuteSequence",
366n/a [(machine_action, machine_prop, start),
367n/a (user_action, user_prop, start + 1),
368n/a (exe_action, None, start + 2),
369n/a ])
370n/a add_data(self.db, "InstallUISequence",
371n/a [(machine_action, machine_prop, start),
372n/a (user_action, user_prop, start + 1),
373n/a (exe_action, None, start + 2),
374n/a ])
375n/a add_data(self.db, "Condition",
376n/a [("Python" + ver, 0, "NOT TARGETDIR" + ver)])
377n/a start += 4
378n/a assert start < 500
379n/a
380n/a def add_scripts(self):
381n/a if self.install_script:
382n/a start = 6800
383n/a for ver in self.versions + [self.other_version]:
384n/a install_action = "install_script." + ver
385n/a exe_prop = "PYTHON" + ver
386n/a add_data(self.db, "CustomAction",
387n/a [(install_action, 50, exe_prop, self.install_script_key)])
388n/a add_data(self.db, "InstallExecuteSequence",
389n/a [(install_action, "&Python%s=3" % ver, start)])
390n/a start += 1
391n/a # XXX pre-install scripts are currently refused in finalize_options()
392n/a # but if this feature is completed, it will also need to add
393n/a # entries for each version as the above code does
394n/a if self.pre_install_script:
395n/a scriptfn = os.path.join(self.bdist_dir, "preinstall.bat")
396n/a with open(scriptfn, "w") as f:
397n/a # The batch file will be executed with [PYTHON], so that %1
398n/a # is the path to the Python interpreter; %0 will be the path
399n/a # of the batch file.
400n/a # rem ="""
401n/a # %1 %0
402n/a # exit
403n/a # """
404n/a # <actual script>
405n/a f.write('rem ="""\n%1 %0\nexit\n"""\n')
406n/a with open(self.pre_install_script) as fp:
407n/a f.write(fp.read())
408n/a add_data(self.db, "Binary",
409n/a [("PreInstall", msilib.Binary(scriptfn)),
410n/a ])
411n/a add_data(self.db, "CustomAction",
412n/a [("PreInstall", 2, "PreInstall", None),
413n/a ])
414n/a add_data(self.db, "InstallExecuteSequence",
415n/a [("PreInstall", "NOT Installed", 450),
416n/a ])
417n/a
418n/a def add_ui(self):
419n/a db = self.db
420n/a x = y = 50
421n/a w = 370
422n/a h = 300
423n/a title = "[ProductName] Setup"
424n/a
425n/a # see "Dialog Style Bits"
426n/a modal = 3 # visible | modal
427n/a modeless = 1 # visible
428n/a
429n/a # UI customization properties
430n/a add_data(db, "Property",
431n/a # See "DefaultUIFont Property"
432n/a [("DefaultUIFont", "DlgFont8"),
433n/a # See "ErrorDialog Style Bit"
434n/a ("ErrorDialog", "ErrorDlg"),
435n/a ("Progress1", "Install"), # modified in maintenance type dlg
436n/a ("Progress2", "installs"),
437n/a ("MaintenanceForm_Action", "Repair"),
438n/a # possible values: ALL, JUSTME
439n/a ("WhichUsers", "ALL")
440n/a ])
441n/a
442n/a # Fonts, see "TextStyle Table"
443n/a add_data(db, "TextStyle",
444n/a [("DlgFont8", "Tahoma", 9, None, 0),
445n/a ("DlgFontBold8", "Tahoma", 8, None, 1), #bold
446n/a ("VerdanaBold10", "Verdana", 10, None, 1),
447n/a ("VerdanaRed9", "Verdana", 9, 255, 0),
448n/a ])
449n/a
450n/a # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table"
451n/a # Numbers indicate sequence; see sequence.py for how these action integrate
452n/a add_data(db, "InstallUISequence",
453n/a [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140),
454n/a ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141),
455n/a # In the user interface, assume all-users installation if privileged.
456n/a ("SelectFeaturesDlg", "Not Installed", 1230),
457n/a # XXX no support for resume installations yet
458n/a #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240),
459n/a ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250),
460n/a ("ProgressDlg", None, 1280)])
461n/a
462n/a add_data(db, 'ActionText', text.ActionText)
463n/a add_data(db, 'UIText', text.UIText)
464n/a #####################################################################
465n/a # Standard dialogs: FatalError, UserExit, ExitDialog
466n/a fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title,
467n/a "Finish", "Finish", "Finish")
468n/a fatal.title("[ProductName] Installer ended prematurely")
469n/a fatal.back("< Back", "Finish", active = 0)
470n/a fatal.cancel("Cancel", "Back", active = 0)
471n/a fatal.text("Description1", 15, 70, 320, 80, 0x30003,
472n/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.")
473n/a fatal.text("Description2", 15, 155, 320, 20, 0x30003,
474n/a "Click the Finish button to exit the Installer.")
475n/a c=fatal.next("Finish", "Cancel", name="Finish")
476n/a c.event("EndDialog", "Exit")
477n/a
478n/a user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title,
479n/a "Finish", "Finish", "Finish")
480n/a user_exit.title("[ProductName] Installer was interrupted")
481n/a user_exit.back("< Back", "Finish", active = 0)
482n/a user_exit.cancel("Cancel", "Back", active = 0)
483n/a user_exit.text("Description1", 15, 70, 320, 80, 0x30003,
484n/a "[ProductName] setup was interrupted. Your system has not been modified. "
485n/a "To install this program at a later time, please run the installation again.")
486n/a user_exit.text("Description2", 15, 155, 320, 20, 0x30003,
487n/a "Click the Finish button to exit the Installer.")
488n/a c = user_exit.next("Finish", "Cancel", name="Finish")
489n/a c.event("EndDialog", "Exit")
490n/a
491n/a exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title,
492n/a "Finish", "Finish", "Finish")
493n/a exit_dialog.title("Completing the [ProductName] Installer")
494n/a exit_dialog.back("< Back", "Finish", active = 0)
495n/a exit_dialog.cancel("Cancel", "Back", active = 0)
496n/a exit_dialog.text("Description", 15, 235, 320, 20, 0x30003,
497n/a "Click the Finish button to exit the Installer.")
498n/a c = exit_dialog.next("Finish", "Cancel", name="Finish")
499n/a c.event("EndDialog", "Return")
500n/a
501n/a #####################################################################
502n/a # Required dialog: FilesInUse, ErrorDlg
503n/a inuse = PyDialog(db, "FilesInUse",
504n/a x, y, w, h,
505n/a 19, # KeepModeless|Modal|Visible
506n/a title,
507n/a "Retry", "Retry", "Retry", bitmap=False)
508n/a inuse.text("Title", 15, 6, 200, 15, 0x30003,
509n/a r"{\DlgFontBold8}Files in Use")
510n/a inuse.text("Description", 20, 23, 280, 20, 0x30003,
511n/a "Some files that need to be updated are currently in use.")
512n/a inuse.text("Text", 20, 55, 330, 50, 3,
513n/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.")
514n/a inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess",
515n/a None, None, None)
516n/a c=inuse.back("Exit", "Ignore", name="Exit")
517n/a c.event("EndDialog", "Exit")
518n/a c=inuse.next("Ignore", "Retry", name="Ignore")
519n/a c.event("EndDialog", "Ignore")
520n/a c=inuse.cancel("Retry", "Exit", name="Retry")
521n/a c.event("EndDialog","Retry")
522n/a
523n/a # See "Error Dialog". See "ICE20" for the required names of the controls.
524n/a error = Dialog(db, "ErrorDlg",
525n/a 50, 10, 330, 101,
526n/a 65543, # Error|Minimize|Modal|Visible
527n/a title,
528n/a "ErrorText", None, None)
529n/a error.text("ErrorText", 50,9,280,48,3, "")
530n/a #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None)
531n/a error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo")
532n/a error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes")
533n/a error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort")
534n/a error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel")
535n/a error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore")
536n/a error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk")
537n/a error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry")
538n/a
539n/a #####################################################################
540n/a # Global "Query Cancel" dialog
541n/a cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title,
542n/a "No", "No", "No")
543n/a cancel.text("Text", 48, 15, 194, 30, 3,
544n/a "Are you sure you want to cancel [ProductName] installation?")
545n/a #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
546n/a # "py.ico", None, None)
547n/a c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No")
548n/a c.event("EndDialog", "Exit")
549n/a
550n/a c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes")
551n/a c.event("EndDialog", "Return")
552n/a
553n/a #####################################################################
554n/a # Global "Wait for costing" dialog
555n/a costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title,
556n/a "Return", "Return", "Return")
557n/a costing.text("Text", 48, 15, 194, 30, 3,
558n/a "Please wait while the installer finishes determining your disk space requirements.")
559n/a c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None)
560n/a c.event("EndDialog", "Exit")
561n/a
562n/a #####################################################################
563n/a # Preparation dialog: no user input except cancellation
564n/a prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title,
565n/a "Cancel", "Cancel", "Cancel")
566n/a prep.text("Description", 15, 70, 320, 40, 0x30003,
567n/a "Please wait while the Installer prepares to guide you through the installation.")
568n/a prep.title("Welcome to the [ProductName] Installer")
569n/a c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...")
570n/a c.mapping("ActionText", "Text")
571n/a c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None)
572n/a c.mapping("ActionData", "Text")
573n/a prep.back("Back", None, active=0)
574n/a prep.next("Next", None, active=0)
575n/a c=prep.cancel("Cancel", None)
576n/a c.event("SpawnDialog", "CancelDlg")
577n/a
578n/a #####################################################################
579n/a # Feature (Python directory) selection
580n/a seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title,
581n/a "Next", "Next", "Cancel")
582n/a seldlg.title("Select Python Installations")
583n/a
584n/a seldlg.text("Hint", 15, 30, 300, 20, 3,
585n/a "Select the Python locations where %s should be installed."
586n/a % self.distribution.get_fullname())
587n/a
588n/a seldlg.back("< Back", None, active=0)
589n/a c = seldlg.next("Next >", "Cancel")
590n/a order = 1
591n/a c.event("[TARGETDIR]", "[SourceDir]", ordering=order)
592n/a for version in self.versions + [self.other_version]:
593n/a order += 1
594n/a c.event("[TARGETDIR]", "[TARGETDIR%s]" % version,
595n/a "FEATURE_SELECTED AND &Python%s=3" % version,
596n/a ordering=order)
597n/a c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1)
598n/a c.event("EndDialog", "Return", ordering=order + 2)
599n/a c = seldlg.cancel("Cancel", "Features")
600n/a c.event("SpawnDialog", "CancelDlg")
601n/a
602n/a c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3,
603n/a "FEATURE", None, "PathEdit", None)
604n/a c.event("[FEATURE_SELECTED]", "1")
605n/a ver = self.other_version
606n/a install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver
607n/a dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver
608n/a
609n/a c = seldlg.text("Other", 15, 200, 300, 15, 3,
610n/a "Provide an alternate Python location")
611n/a c.condition("Enable", install_other_cond)
612n/a c.condition("Show", install_other_cond)
613n/a c.condition("Disable", dont_install_other_cond)
614n/a c.condition("Hide", dont_install_other_cond)
615n/a
616n/a c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1,
617n/a "TARGETDIR" + ver, None, "Next", None)
618n/a c.condition("Enable", install_other_cond)
619n/a c.condition("Show", install_other_cond)
620n/a c.condition("Disable", dont_install_other_cond)
621n/a c.condition("Hide", dont_install_other_cond)
622n/a
623n/a #####################################################################
624n/a # Disk cost
625n/a cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title,
626n/a "OK", "OK", "OK", bitmap=False)
627n/a cost.text("Title", 15, 6, 200, 15, 0x30003,
628n/a "{\DlgFontBold8}Disk Space Requirements")
629n/a cost.text("Description", 20, 20, 280, 20, 0x30003,
630n/a "The disk space required for the installation of the selected features.")
631n/a cost.text("Text", 20, 53, 330, 60, 3,
632n/a "The highlighted volumes (if any) do not have enough disk space "
633n/a "available for the currently selected features. You can either "
634n/a "remove some files from the highlighted volumes, or choose to "
635n/a "install less features onto local drive(s), or select different "
636n/a "destination drive(s).")
637n/a cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223,
638n/a None, "{120}{70}{70}{70}{70}", None, None)
639n/a cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return")
640n/a
641n/a #####################################################################
642n/a # WhichUsers Dialog. Only available on NT, and for privileged users.
643n/a # This must be run before FindRelatedProducts, because that will
644n/a # take into account whether the previous installation was per-user
645n/a # or per-machine. We currently don't support going back to this
646n/a # dialog after "Next" was selected; to support this, we would need to
647n/a # find how to reset the ALLUSERS property, and how to re-run
648n/a # FindRelatedProducts.
649n/a # On Windows9x, the ALLUSERS property is ignored on the command line
650n/a # and in the Property table, but installer fails according to the documentation
651n/a # if a dialog attempts to set ALLUSERS.
652n/a whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title,
653n/a "AdminInstall", "Next", "Cancel")
654n/a whichusers.title("Select whether to install [ProductName] for all users of this computer.")
655n/a # A radio group with two options: allusers, justme
656n/a g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3,
657n/a "WhichUsers", "", "Next")
658n/a g.add("ALL", 0, 5, 150, 20, "Install for all users")
659n/a g.add("JUSTME", 0, 25, 150, 20, "Install just for me")
660n/a
661n/a whichusers.back("Back", None, active=0)
662n/a
663n/a c = whichusers.next("Next >", "Cancel")
664n/a c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1)
665n/a c.event("EndDialog", "Return", ordering = 2)
666n/a
667n/a c = whichusers.cancel("Cancel", "AdminInstall")
668n/a c.event("SpawnDialog", "CancelDlg")
669n/a
670n/a #####################################################################
671n/a # Installation Progress dialog (modeless)
672n/a progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title,
673n/a "Cancel", "Cancel", "Cancel", bitmap=False)
674n/a progress.text("Title", 20, 15, 200, 15, 0x30003,
675n/a "{\DlgFontBold8}[Progress1] [ProductName]")
676n/a progress.text("Text", 35, 65, 300, 30, 3,
677n/a "Please wait while the Installer [Progress2] [ProductName]. "
678n/a "This may take several minutes.")
679n/a progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:")
680n/a
681n/a c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...")
682n/a c.mapping("ActionText", "Text")
683n/a
684n/a #c=progress.text("ActionData", 35, 140, 300, 20, 3, None)
685n/a #c.mapping("ActionData", "Text")
686n/a
687n/a c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537,
688n/a None, "Progress done", None, None)
689n/a c.mapping("SetProgress", "Progress")
690n/a
691n/a progress.back("< Back", "Next", active=False)
692n/a progress.next("Next >", "Cancel", active=False)
693n/a progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg")
694n/a
695n/a ###################################################################
696n/a # Maintenance type: repair/uninstall
697n/a maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title,
698n/a "Next", "Next", "Cancel")
699n/a maint.title("Welcome to the [ProductName] Setup Wizard")
700n/a maint.text("BodyText", 15, 63, 330, 42, 3,
701n/a "Select whether you want to repair or remove [ProductName].")
702n/a g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3,
703n/a "MaintenanceForm_Action", "", "Next")
704n/a #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]")
705n/a g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]")
706n/a g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]")
707n/a
708n/a maint.back("< Back", None, active=False)
709n/a c=maint.next("Finish", "Cancel")
710n/a # Change installation: Change progress dialog to "Change", then ask
711n/a # for feature selection
712n/a #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1)
713n/a #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2)
714n/a
715n/a # Reinstall: Change progress dialog to "Repair", then invoke reinstall
716n/a # Also set list of reinstalled features to "ALL"
717n/a c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5)
718n/a c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6)
719n/a c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7)
720n/a c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8)
721n/a
722n/a # Uninstall: Change progress to "Remove", then invoke uninstall
723n/a # Also set list of removed features to "ALL"
724n/a c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11)
725n/a c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12)
726n/a c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13)
727n/a c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14)
728n/a
729n/a # Close dialog when maintenance action scheduled
730n/a c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20)
731n/a #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21)
732n/a
733n/a maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg")
734n/a
735n/a def get_installer_filename(self, fullname):
736n/a # Factored out to allow overriding in subclasses
737n/a if self.target_version:
738n/a base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name,
739n/a self.target_version)
740n/a else:
741n/a base_name = "%s.%s.msi" % (fullname, self.plat_name)
742n/a installer_name = os.path.join(self.dist_dir, base_name)
743n/a return installer_name