Index: Lib/distutils/command/bdist_msi.py =================================================================== --- Lib/distutils/command/bdist_msi.py (revision 71386) +++ Lib/distutils/command/bdist_msi.py (working copy) @@ -141,7 +141,7 @@ "target version can only be %s, or the '--skip_build'" " option must be specified" % (short_version,)) else: - self.target_version = short_version + self.target_version = "" self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'), @@ -219,11 +219,7 @@ # ProductVersion must be strictly numeric # XXX need to deal with prerelease versions sversion = "%d.%d.%d" % StrictVersion(version).version - # 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()) + product_name = self.distribution.get_fullname() self.db = msilib.init_database(installer_name, schema, product_name, msilib.gen_uuid(), sversion, author) @@ -244,7 +240,11 @@ self.db.Commit() if hasattr(self.distribution, 'dist_files'): - self.distribution.dist_files.append(('bdist_msi', self.target_version, fullname)) + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + self.distribution.dist_files.append(('bdist_msi', pyversion, fullname)) if not self.keep_temp: remove_tree(self.bdist_dir, dry_run=self.dry_run) @@ -277,33 +277,47 @@ 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")]) + + The FindPythonDirs action finds Python installations in the registry, + and fills in the TARGETDIR rows of the ListView table. Additionally, + the last Python installation path found is stored in PYTHONDIR. + + The SetTargetDir action sets TARGETDIR from PYTHONDIR. (Usually done + only if TARGETDIR was not already specified.) + + The SetPythonExe action sets PYTHON from TARGETDIR. (Usually done only + if PYTHON was not already specified.) + + The SetProductName action prefixes the product name with "Python X.Y" + so that it sorts together with the other Python packages in + Add-Remove-Programs (APR) + + These actions are added to both the install execute sequence and the + install UI sequence. + """ + vbs_path = os.path.join(os.path.dirname(__file__), "PythonActions.vbs") + add_data(self.db, "Property", + [("PythonVersion", self.target_version or 'any')]) + add_data(self.db, "Binary", + [("PythonActions", msilib.Binary(vbs_path))]) 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]")]) + [("FindPythonDirs", 6+256, "PythonActions", "FindPythonDirs"), + ("SetTargetDir", 51+256, "TARGETDIR", "[PYTHONDIR]"), + ("SetPythonExe", 51+256, "PYTHON", "[TARGETDIR]\\python.exe"), + ("SetProductName", 6+256, "PythonActions", "SetProductName"), + ]) add_data(self.db, "InstallExecuteSequence", - [("PythonFromMachine", "PYTHON.MACHINE", 401), - ("PythonFromUser", "PYTHON.USER", 402), - ("PythonExe", None, 403), - ("InitialTargetDir", 'TARGETDIR=""', 404), + [("FindPythonDirs", None, 401), + ("SetTargetDir", 'TARGETDIR=""', 402), + ("SetPythonExe", 'PYTHON=""', 1231), + ("SetProductName", None, 1232), ]) add_data(self.db, "InstallUISequence", - [("PythonFromMachine", "PYTHON.MACHINE", 401), - ("PythonFromUser", "PYTHON.USER", 402), - ("PythonExe", None, 403), - ("InitialTargetDir", 'TARGETDIR=""', 404), + [("FindPythonDirs", None, 401), + ("SetTargetDir", 'TARGETDIR=""', 402), + # after SelectDirectoryDlg + ("SetPythonExe", 'PYTHON=""', 1231), + ("SetProductName", None, 1232), ]) def add_scripts(self): @@ -502,30 +516,25 @@ seldlg = PyDialog(db, "SelectDirectoryDlg", x, y, w, h, modal, title, "Next", "Next", "Cancel") seldlg.title("Select Destination Directory") - - version = sys.version[:3]+" " - seldlg.text("Hint", 15, 30, 300, 40, 3, - "The destination directory should contain a Python %sinstallation" % version) - - seldlg.back("< Back", None, active=0) + hint = "The destination directory should contain a Python " + if self.target_version: + hint += "%s installation" % self.target_version + else: + hint += "installation" + seldlg.text("Hint", 15, 30, 300, 40, 3, hint) + c = seldlg.control("ListView", "ListView", 15, 90, 300, 90, 3, + "TARGETDIR", None, "PathEdit", None) + c.event("SetTargetPath", "TARGETDIR") + seldlg.control("PathEdit", "PathEdit", 15, 190, 300, 18, 3, + "TARGETDIR", None, "Back", None) + seldlg.back("< Back", "Next", 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") + c = seldlg.cancel("Cancel", "ListView") 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") - + ##################################################################### # Disk cost cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, @@ -640,7 +649,10 @@ 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: + tup = fullname, self.plat_name, self.target_version + base_name = "%s.%s-py%s.msi" % tup + else: + base_name = "%s.%s.msi" % (fullname, self.plat_name) installer_name = os.path.join(self.dist_dir, base_name) return installer_name Index: Lib/distutils/command/PythonActions.vbs =================================================================== --- Lib/distutils/command/PythonActions.vbs (revision 0) +++ Lib/distutils/command/PythonActions.vbs (revision 0) @@ -0,0 +1,103 @@ +Option Explicit + +Sub FindPythonDirs + + ' key constants + const HKEY_CURRENT_USER = &H80000001 + const HKEY_LOCAL_MACHINE = &H80000002 + + ' open up the registry + Dim reg + Set reg = GetObject(_ + "winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv") + + ' get the declared Python version + Dim requiredVersion + requiredVersion = Session.Property("PythonVersion") + + ' a mapping from version names to Python installation directory paths + Dim installPaths + Set installPaths = CreateObject("Scripting.Dictionary") + + ' add local machine values first, then overwrite with any current user values + Dim rootKey, rootKeys(1) + rootKeys(0) = HKEY_LOCAL_MACHINE + rootKeys(1) = HKEY_CURRENT_USER + For Each rootKey in rootKeys + + ' update the mapping for each version listed in the registry + Dim version, versions + reg.EnumKey rootKey, "SOFTWARE\Python\PythonCore", versions + If Not IsNull(versions) Then + For Each version In versions + + ' skip versions incompatible with the declared version + If (requiredVersion = "any") Or (version = requiredVersion) Then + + ' get the installation path for this Python version + Dim subKeyName, installPath + subKeyName = "SOFTWARE\Python\PythonCore\" & version & "\InstallPath" + reg.GetStringValue rootKey, subKeyName, "", installPath + + ' if the path was present, update the mapping + If Not IsNull(installPath) Then + installPaths.Item(version) = installPath + End If + + End If + Next + End If + Next + + ' insert install paths into the ListView table + Dim order + order = -1 + For Each version In installPaths.Keys + installPath = installPaths.Item(version) + order = order + 1 + + ' insert the install path into the ListView table + Dim query, params, view + query = "INSERT INTO `ListView` (`Property`, `Order`, `Value`, `Text`) " &_ + "VALUES ('TARGETDIR', ?, ?, ?) TEMPORARY" + Set params = Session.Installer.CreateRecord(3) + params.IntegerData(1) = order + params.StringData(2) = installPath + params.StringData(3) = "Python " & version & " (found in registry)" + Set view = Session.Database.OpenView(query) + view.Execute params + + ' set the default PYTHONDIR to the last installed Python + Session.Property("PYTHONDIR") = installPath + Next +End Sub + +Sub SetProductName + ' Find the text including the Python version that was selected + Dim query, params, view, record, text + query = "SELECT `Text` FROM `ListView` WHERE `Value`=?" + Set params = Session.Installer.CreateRecord(1) + params.StringData(1) = Session.Property("TARGETDIR") + Set view = Session.Database.OpenView(query) + view.Execute(params) + Set record = view.Fetch + If record.IsNull(1) Then + text = "" + Else + text = record.StringData(1) + End If + + ' Extract "Python X.Y" from the string + Dim pythonRegexp, pythonName, match + pythonName = "Python" + Set pythonRegexp = New RegExp + pythonRegexp.Pattern = "Python \d+\.\d+" + For Each match in pythonRegexp.Execute(text) + pythonName = match.value + Next + + ' Prefix the product name with "Python X.Y " + Dim productName + productName = Session.Property("ProductName") + Session.Property("ProductName") = pythonName & " " & productName +End Sub \ No newline at end of file