summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorSteven Bethard <steven.bethard@gmail.com>2009-05-05 02:22:38 (GMT)
committerSteven Bethard <steven.bethard@gmail.com>2009-05-05 02:22:38 (GMT)
commit93f65a177b36396dddd1e2938cc037288a7eb400 (patch)
tree24e639d606125154c42495ef89123b436aaa708b /Lib
parentf877feb8063e61b910671ce5e391d96f4a3a2d9f (diff)
downloadcpython-93f65a177b36396dddd1e2938cc037288a7eb400.zip
cpython-93f65a177b36396dddd1e2938cc037288a7eb400.tar.gz
cpython-93f65a177b36396dddd1e2938cc037288a7eb400.tar.bz2
Merged revisions 72306 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r72306 | steven.bethard | 2009-05-04 18:31:22 -0700 (Mon, 04 May 2009) | 1 line Update bdist_msi so that the generated MSIs for pure Python modules can install to any version of Python, like the generated EXEs from bdist_wininst. (Previously, you had to create a new MSI for each version of Python.) ........
Diffstat (limited to 'Lib')
-rw-r--r--Lib/distutils/command/bdist_msi.py240
1 files changed, 163 insertions, 77 deletions
diff --git a/Lib/distutils/command/bdist_msi.py b/Lib/distutils/command/bdist_msi.py
index aab89cd..ffd668e 100644
--- a/Lib/distutils/command/bdist_msi.py
+++ b/Lib/distutils/command/bdist_msi.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright (C) 2005, 2006 Martin v. Löwis
+# Copyright (C) 2005, 2006 Martin v. Löwis
# Licensed to PSF under a Contributor Agreement.
# The bdist_wininst command proper
# based on bdist_wininst
@@ -117,6 +117,12 @@ class bdist_msi(Command):
boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
'skip-build']
+ all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4',
+ '2.5', '2.6', '2.7', '2.8', '2.9',
+ '3.0', '3.1', '3.2', '3.3', '3.4',
+ '3.5', '3.6', '3.7', '3.8', '3.9']
+ other_version = 'X'
+
def initialize_options(self):
self.bdist_dir = None
self.plat_name = None
@@ -128,6 +134,7 @@ class bdist_msi(Command):
self.skip_build = 0
self.install_script = None
self.pre_install_script = None
+ self.versions = None
def finalize_options(self):
if self.bdist_dir is None:
@@ -135,13 +142,14 @@ class bdist_msi(Command):
self.bdist_dir = os.path.join(bdist_base, 'msi')
short_version = get_python_version()
if self.target_version:
+ self.versions = [self.target_version]
if not self.skip_build and self.distribution.has_ext_modules()\
and self.target_version != short_version:
raise DistutilsOptionError(
"target version can only be %s, or the '--skip_build'"
" option must be specified" % (short_version,))
else:
- self.target_version = short_version
+ self.versions = list(self.all_versions)
self.set_undefined_options('bdist',
('dist_dir', 'dist_dir'),
@@ -222,8 +230,11 @@ class bdist_msi(Command):
# Prefix ProductName with Python x.y, so that
# it sorts together with the other Python packages
# in Add-Remove-Programs (APR)
- product_name = "Python %s %s" % (self.target_version,
- self.distribution.get_fullname())
+ fullname = self.distribution.get_fullname()
+ if self.target_version:
+ product_name = "Python %s %s" % (self.target_version, fullname)
+ else:
+ product_name = "Python %s" % (fullname)
self.db = msilib.init_database(installer_name, schema,
product_name, msilib.gen_uuid(),
sversion, author)
@@ -244,7 +255,8 @@ class bdist_msi(Command):
self.db.Commit()
if hasattr(self.distribution, 'dist_files'):
- self.distribution.dist_files.append(('bdist_msi', self.target_version, fullname))
+ tup = 'bdist_msi', self.target_version or 'any', fullname
+ self.distribution.dist_files.append(tup)
if not self.keep_temp:
remove_tree(self.bdist_dir, dry_run=self.dry_run)
@@ -252,66 +264,121 @@ class bdist_msi(Command):
def add_files(self):
db = self.db
cab = msilib.CAB("distfiles")
- f = Feature(db, "default", "Default Feature", "Everything", 1, directory="TARGETDIR")
- f.set_current()
rootdir = os.path.abspath(self.bdist_dir)
+
root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir")
+ f = Feature(db, "Python", "Python", "Everything",
+ 0, 1, directory="TARGETDIR")
+
+ items = [(f, root, '')]
+ for version in self.versions + [self.other_version]:
+ target = "TARGETDIR" + version
+ name = default = "Python" + version
+ desc = "Everything"
+ if version is self.other_version:
+ title = "Python from another location"
+ level = 2
+ else:
+ title = "Python %s from registry" % version
+ level = 1
+ f = Feature(db, name, title, desc, 1, level, directory=target)
+ dir = Directory(db, cab, root, rootdir, target, default)
+ items.append((f, dir, version))
db.Commit()
- todo = [root]
- while todo:
- dir = todo.pop()
- for file in os.listdir(dir.absolute):
- afile = os.path.join(dir.absolute, file)
- if os.path.isdir(afile):
- newdir = Directory(db, cab, dir, file, file, "%s|%s" % (dir.make_short(file), file))
- todo.append(newdir)
- else:
- key = dir.add_file(file)
- if file==self.install_script:
- if self.install_script_key:
- raise DistutilsOptionError(
- "Multiple files with name %s" % file)
- self.install_script_key = '[#%s]' % key
+
+ seen = {}
+ for feature, dir, version in items:
+ todo = [dir]
+ while todo:
+ dir = todo.pop()
+ for file in os.listdir(dir.absolute):
+ afile = os.path.join(dir.absolute, file)
+ if os.path.isdir(afile):
+ short = "%s|%s" % (dir.make_short(file), file)
+ default = file + version
+ newdir = Directory(db, cab, dir, file, default, short)
+ todo.append(newdir)
+ else:
+ if not dir.component:
+ dir.start_component(dir.logical, feature, 0)
+ if afile not in seen:
+ key = seen[afile] = dir.add_file(file)
+ if file==self.install_script:
+ if self.install_script_key:
+ raise DistutilsOptionError(
+ "Multiple files with name %s" % file)
+ self.install_script_key = '[#%s]' % key
+ else:
+ key = seen[afile]
+ add_data(self.db, "DuplicateFile",
+ [(key + version, dir.component, key, None, dir.logical)])
+
cab.commit(db)
def add_find_python(self):
"""Adds code to the installer to compute the location of Python.
- Properties PYTHON.MACHINE, PYTHON.USER, PYTHONDIR and PYTHON will be set
- in both the execute and UI sequences; PYTHONDIR will be set from
- PYTHON.USER if defined, else from PYTHON.MACHINE.
- PYTHON is PYTHONDIR\python.exe"""
- install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % self.target_version
- add_data(self.db, "RegLocator",
- [("python.machine", 2, install_path, None, 2),
- ("python.user", 1, install_path, None, 2)])
- add_data(self.db, "AppSearch",
- [("PYTHON.MACHINE", "python.machine"),
- ("PYTHON.USER", "python.user")])
- add_data(self.db, "CustomAction",
- [("PythonFromMachine", 51+256, "PYTHONDIR", "[PYTHON.MACHINE]"),
- ("PythonFromUser", 51+256, "PYTHONDIR", "[PYTHON.USER]"),
- ("PythonExe", 51+256, "PYTHON", "[PYTHONDIR]\\python.exe"),
- ("InitialTargetDir", 51+256, "TARGETDIR", "[PYTHONDIR]")])
- add_data(self.db, "InstallExecuteSequence",
- [("PythonFromMachine", "PYTHON.MACHINE", 401),
- ("PythonFromUser", "PYTHON.USER", 402),
- ("PythonExe", None, 403),
- ("InitialTargetDir", 'TARGETDIR=""', 404),
- ])
- add_data(self.db, "InstallUISequence",
- [("PythonFromMachine", "PYTHON.MACHINE", 401),
- ("PythonFromUser", "PYTHON.USER", 402),
- ("PythonExe", None, 403),
- ("InitialTargetDir", 'TARGETDIR=""', 404),
- ])
- def add_scripts(self):
- if self.install_script:
+ Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the
+ registry for each version of Python.
+
+ Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined,
+ else from PYTHON.MACHINE.X.Y.
+
+ Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe"""
+
+ start = 402
+ for ver in self.versions:
+ install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver
+ machine_reg = "python.machine." + ver
+ user_reg = "python.user." + ver
+ machine_prop = "PYTHON.MACHINE." + ver
+ user_prop = "PYTHON.USER." + ver
+ machine_action = "PythonFromMachine" + ver
+ user_action = "PythonFromUser" + ver
+ exe_action = "PythonExe" + ver
+ target_dir_prop = "TARGETDIR" + ver
+ exe_prop = "PYTHON" + ver
+ add_data(self.db, "RegLocator",
+ [(machine_reg, 2, install_path, None, 2),
+ (user_reg, 1, install_path, None, 2)])
+ add_data(self.db, "AppSearch",
+ [(machine_prop, machine_reg),
+ (user_prop, user_reg)])
add_data(self.db, "CustomAction",
- [("install_script", 50, "PYTHON", self.install_script_key)])
+ [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"),
+ (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"),
+ (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"),
+ ])
add_data(self.db, "InstallExecuteSequence",
- [("install_script", "NOT Installed", 6800)])
+ [(machine_action, machine_prop, start),
+ (user_action, user_prop, start + 1),
+ (exe_action, None, start + 2),
+ ])
+ add_data(self.db, "InstallUISequence",
+ [(machine_action, machine_prop, start),
+ (user_action, user_prop, start + 1),
+ (exe_action, None, start + 2),
+ ])
+ add_data(self.db, "Condition",
+ [("Python" + ver, 0, "NOT TARGETDIR" + ver)])
+ start += 4
+ assert start < 500
+
+ def add_scripts(self):
+ if self.install_script:
+ start = 6800
+ for ver in self.versions + [self.other_version]:
+ install_action = "install_script." + ver
+ exe_prop = "PYTHON" + ver
+ add_data(self.db, "CustomAction",
+ [(install_action, 50, exe_prop, self.install_script_key)])
+ add_data(self.db, "InstallExecuteSequence",
+ [(install_action, "&Python%s=3" % ver, start)])
+ start += 1
+ # XXX pre-install scripts are currently refused in finalize_options()
+ # but if this feature is completed, it will also need to add
+ # entries for each version as the above code does
if self.pre_install_script:
scriptfn = os.path.join(self.bdist_dir, "preinstall.bat")
f = open(scriptfn, "w")
@@ -375,7 +442,7 @@ class bdist_msi(Command):
[("PrepareDlg", "Not Privileged or Windows9x or Installed", 140),
("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141),
# In the user interface, assume all-users installation if privileged.
- ("SelectDirectoryDlg", "Not Installed", 1230),
+ ("SelectFeaturesDlg", "Not Installed", 1230),
# XXX no support for resume installations yet
#("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240),
("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250),
@@ -498,33 +565,49 @@ class bdist_msi(Command):
c.event("SpawnDialog", "CancelDlg")
#####################################################################
- # Target directory selection
- seldlg = PyDialog(db, "SelectDirectoryDlg", x, y, w, h, modal, title,
+ # Feature (Python directory) selection
+ seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title,
"Next", "Next", "Cancel")
- seldlg.title("Select Destination Directory")
+ seldlg.title("Select Python Installations")
- version = sys.version[:3]+" "
- seldlg.text("Hint", 15, 30, 300, 40, 3,
- "The destination directory should contain a Python %sinstallation" % version)
+ seldlg.text("Hint", 15, 30, 300, 20, 3,
+ "Select the Python locations where %s should be installed."
+ % self.distribution.get_fullname())
seldlg.back("< Back", None, active=0)
c = seldlg.next("Next >", "Cancel")
- c.event("SetTargetPath", "TARGETDIR", ordering=1)
- c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=2)
- c.event("EndDialog", "Return", ordering=3)
-
- c = seldlg.cancel("Cancel", "DirectoryCombo")
+ order = 1
+ c.event("[TARGETDIR]", "[SourceDir]", ordering=order)
+ for version in self.versions + [self.other_version]:
+ order += 1
+ c.event("[TARGETDIR]", "[TARGETDIR%s]" % version,
+ "FEATURE_SELECTED AND &Python%s=3" % version,
+ ordering=order)
+ c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1)
+ c.event("EndDialog", "Return", ordering=order + 2)
+ c = seldlg.cancel("Cancel", "Features")
c.event("SpawnDialog", "CancelDlg")
- seldlg.control("DirectoryCombo", "DirectoryCombo", 15, 70, 272, 80, 393219,
- "TARGETDIR", None, "DirectoryList", None)
- seldlg.control("DirectoryList", "DirectoryList", 15, 90, 308, 136, 3, "TARGETDIR",
- None, "PathEdit", None)
- seldlg.control("PathEdit", "PathEdit", 15, 230, 306, 16, 3, "TARGETDIR", None, "Next", None)
- c = seldlg.pushbutton("Up", 306, 70, 18, 18, 3, "Up", None)
- c.event("DirectoryListUp", "0")
- c = seldlg.pushbutton("NewDir", 324, 70, 30, 18, 3, "New", None)
- c.event("DirectoryListNew", "0")
+ c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3,
+ "FEATURE", None, "PathEdit", None)
+ c.event("[FEATURE_SELECTED]", "1")
+ ver = self.other_version
+ install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver
+ dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver
+
+ c = seldlg.text("Other", 15, 200, 300, 15, 3,
+ "Provide an alternate Python location")
+ c.condition("Enable", install_other_cond)
+ c.condition("Show", install_other_cond)
+ c.condition("Disable", dont_install_other_cond)
+ c.condition("Hide", dont_install_other_cond)
+
+ c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1,
+ "TARGETDIR" + ver, None, "Next", None)
+ c.condition("Enable", install_other_cond)
+ c.condition("Show", install_other_cond)
+ c.condition("Disable", dont_install_other_cond)
+ c.condition("Hide", dont_install_other_cond)
#####################################################################
# Disk cost
@@ -640,7 +723,10 @@ class bdist_msi(Command):
def get_installer_filename(self, fullname):
# Factored out to allow overriding in subclasses
- base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name,
- self.target_version)
+ if self.target_version:
+ base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name,
+ self.target_version)
+ else:
+ base_name = "%s.%s.msi" % (fullname, self.plat_name)
installer_name = os.path.join(self.dist_dir, base_name)
return installer_name