diff options
Diffstat (limited to 'Tools/msi/msi.py')
-rw-r--r-- | Tools/msi/msi.py | 270 |
1 files changed, 133 insertions, 137 deletions
diff --git a/Tools/msi/msi.py b/Tools/msi/msi.py index 508816d..2ec6951 100644 --- a/Tools/msi/msi.py +++ b/Tools/msi/msi.py @@ -2,12 +2,11 @@ # (C) 2003 Martin v. Loewis # See "FOO" in comments refers to MSDN sections with the title FOO. import msilib, schema, sequence, os, glob, time, re, shutil, zipfile +import subprocess, tempfile from msilib import Feature, CAB, Directory, Dialog, Binary, add_data import uisample from win32com.client import constants from distutils.spawn import find_executable -from uuids import product_codes -import tempfile # Settings can be overridden in config.py below # 0 for official python.org releases @@ -28,7 +27,7 @@ have_tcl = True # path to PCbuild directory PCBUILD="PCbuild" # msvcrt version -MSVCR = "90" +MSVCR = "100" # Name of certificate in default store to sign MSI with certname = None # Make a zip file containing the PDB files for this build? @@ -77,19 +76,16 @@ upgrade_code_64='{6A965A0C-6EE6-4E3A-9983-3263F56311EC}' if snapshot: current_version = "%s.%s.%s" % (major, minor, int(time.time()/3600/24)) - product_code = msilib.gen_uuid() -else: - product_code = product_codes[current_version] if full_current_version is None: full_current_version = current_version extensions = [ - 'bz2.pyd', 'pyexpat.pyd', 'select.pyd', 'unicodedata.pyd', 'winsound.pyd', + '_bz2.pyd', '_elementtree.pyd', '_socket.pyd', '_ssl.pyd', @@ -100,7 +96,10 @@ extensions = [ '_ctypes_test.pyd', '_sqlite3.pyd', '_hashlib.pyd', - '_multiprocessing.pyd' + '_multiprocessing.pyd', + '_lzma.pyd', + '_decimal.pyd', + '_testbuffer.pyd' ] # Well-known component UUIDs @@ -119,12 +118,11 @@ pythondll_uuid = { "30":"{6953bc3b-6768-4291-8410-7914ce6e2ca8}", "31":"{4afcba0b-13e4-47c3-bebe-477428b46913}", "32":"{3ff95315-1096-4d31-bd86-601d5438ad5e}", + "33":"{f7581ca4-d368-4eea-8f82-d48c64c4f047}", } [major+minor] # Compute the name that Sphinx gives to the docfile -docfile = "" -if int(micro): - docfile = micro +docfile = micro if level < 0xf: if level == 0xC: docfile += "rc%s" % (serial,) @@ -185,12 +183,19 @@ dll_path = os.path.join(srcdir, PCBUILD, dll_file) msilib.set_arch_from_file(dll_path) if msilib.pe_type(dll_path) != msilib.pe_type("msisupport.dll"): raise SystemError("msisupport.dll for incorrect architecture") + if msilib.Win64: upgrade_code = upgrade_code_64 - # Bump the last digit of the code by one, so that 32-bit and 64-bit - # releases get separate product codes - digit = hex((int(product_code[-2],16)+1)%16)[-1] - product_code = product_code[:-2] + digit + '}' + +if snapshot: + product_code = msilib.gen_uuid() +else: + # official release: generate UUID from the download link that the file will have + import uuid + product_code = uuid.uuid3(uuid.NAMESPACE_URL, + 'http://www.python.org/ftp/python/%s.%s.%s/python-%s%s.msi' % + (major, minor, micro, full_current_version, msilib.arch_ext)) + product_code = '{%s}' % product_code if testpackage: ext = 'px' @@ -281,7 +286,7 @@ def remove_old_versions(db): None, migrate_features, None, "REMOVEOLDSNAPSHOT")]) props = "REMOVEOLDSNAPSHOT;REMOVEOLDVERSION" - props += ";TARGETDIR;DLLDIR" + props += ";TARGETDIR;DLLDIR;LAUNCHERDIR" # Installer collects the product codes of the earlier releases in # these properties. In order to allow modification of the properties, # they must be declared as secure. See "SecureCustomProperties Property" @@ -410,7 +415,7 @@ def add_ui(db): ("VerdanaRed9", "Verdana", 9, 255, 0), ]) - compileargs = r'-Wi "[TARGETDIR]Lib\compileall.py" -f -x "bad_coding|badsyntax|site-packages|py2_|lib2to3\\tests" "[TARGETDIR]Lib"' + compileargs = r'-Wi "[TARGETDIR]Lib\compileall.py" -f -x "bad_coding|badsyntax|site-packages|py2_|lib2to3\\tests|venv\\scripts" "[TARGETDIR]Lib"' lib2to3args = r'-c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"' # See "CustomAction Table" add_data(db, "CustomAction", [ @@ -421,6 +426,8 @@ def add_ui(db): "[WindowsVolume]Python%s%s" % (major, minor)), ("SetDLLDirToTarget", 307, "DLLDIR", "[TARGETDIR]"), ("SetDLLDirToSystem32", 307, "DLLDIR", SystemFolderName), + ("SetLauncherDirToTarget", 307, "LAUNCHERDIR", "[TARGETDIR]"), + ("SetLauncherDirToWindows", 307, "LAUNCHERDIR", "[WindowsFolder]"), # msidbCustomActionTypeExe + msidbCustomActionTypeSourceFile # See "Custom Action Type 18" ("CompilePyc", 18, "python.exe", compileargs), @@ -437,6 +444,8 @@ def add_ui(db): # In the user interface, assume all-users installation if privileged. ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751), ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752), + ("SetLauncherDirToWindows", 'LAUNCHERDIR="" and ' + sys32cond, 753), + ("SetLauncherDirToTarget", 'LAUNCHERDIR="" and not ' + sys32cond, 754), ("SelectDirectoryDlg", "Not Installed", 1230), # XXX no support for resume installations yet #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), @@ -445,13 +454,20 @@ def add_ui(db): add_data(db, "AdminUISequence", [("InitialTargetDir", 'TARGETDIR=""', 750), ("SetDLLDirToTarget", 'DLLDIR=""', 751), + ("SetLauncherDirToTarget", 'LAUNCHERDIR=""', 752), ]) + # Prepend TARGETDIR to the system path, and remove it on uninstall. + add_data(db, "Environment", + [("PathAddition", "=-*Path", "[TARGETDIR];[~]", "REGISTRY.path")]) + # Execute Sequences add_data(db, "InstallExecuteSequence", [("InitialTargetDir", 'TARGETDIR=""', 750), ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751), ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752), + ("SetLauncherDirToWindows", 'LAUNCHERDIR="" and ' + sys32cond, 753), + ("SetLauncherDirToTarget", 'LAUNCHERDIR="" and not ' + sys32cond, 754), ("UpdateEditIDLE", None, 1050), ("CompilePyc", "COMPILEALL", 6800), ("CompilePyo", "COMPILEALL", 6801), @@ -460,6 +476,7 @@ def add_ui(db): add_data(db, "AdminExecuteSequence", [("InitialTargetDir", 'TARGETDIR=""', 750), ("SetDLLDirToTarget", 'DLLDIR=""', 751), + ("SetLauncherDirToTarget", 'LAUNCHERDIR=""', 752), ("CompilePyc", "COMPILEALL", 6800), ("CompilePyo", "COMPILEALL", 6801), ("CompileGrammar", "COMPILEALL", 6802), @@ -670,11 +687,11 @@ def add_ui(db): c=features.xbutton("Advanced", "Advanced", None, 0.30) c.event("SpawnDialog", "AdvancedDlg") - c=features.text("ItemDescription", 140, 180, 210, 30, 3, + c=features.text("ItemDescription", 140, 180, 210, 40, 3, "Multiline description of the currently selected item.") c.mapping("SelectionDescription","Text") - c=features.text("ItemSize", 140, 210, 210, 45, 3, + c=features.text("ItemSize", 140, 225, 210, 33, 3, "The size of the currently selected item.") c.mapping("SelectionSize", "Text") @@ -828,7 +845,7 @@ def add_features(db): # (i.e. additional Python libraries) need to follow the parent feature. # Features that have no advertisement trigger (e.g. the test suite) # must not support advertisement - global default_feature, tcltk, htmlfiles, tools, testsuite, ext_feature, private_crt + global default_feature, tcltk, htmlfiles, tools, testsuite, ext_feature, private_crt, prepend_path default_feature = Feature(db, "DefaultFeature", "Python", "Python Interpreter and Libraries", 1, directory = "TARGETDIR") @@ -848,32 +865,38 @@ def add_features(db): htmlfiles = Feature(db, "Documentation", "Documentation", "Python HTMLHelp File", 7, parent = default_feature) tools = Feature(db, "Tools", "Utility Scripts", - "Python utility scripts (Tools/", 9, + "Python utility scripts (Tools/)", 9, parent = default_feature, attributes=2) testsuite = Feature(db, "Testsuite", "Test suite", "Python test suite (Lib/test/)", 11, parent = default_feature, attributes=2|8) - -def extract_msvcr90(): + # prepend_path is an additional feature which is to be off by default. + # Since the default level for the above features is 1, this needs to be + # at least level higher. + prepend_path = Feature(db, "PrependPath", "Add python.exe to Path", + "Prepend [TARGETDIR] to the system Path variable. " + "This allows you to type 'python' into a command " + "prompt without needing the full path.", 13, + parent = default_feature, attributes=2|8, + level=2) + +def extract_msvcr100(): # Find the redistributable files if msilib.Win64: - arch = "amd64" + arch = "x64" else: arch = "x86" - dir = os.path.join(os.environ['VS90COMNTOOLS'], r"..\..\VC\redist\%s\Microsoft.VC90.CRT" % arch) + dir = os.path.join(os.environ['VS100COMNTOOLS'], r"..\..\VC\redist\%s\Microsoft.VC100.CRT" % arch) result = [] installer = msilib.MakeInstaller() - # omit msvcm90 and msvcp90, as they aren't really needed - files = ["Microsoft.VC90.CRT.manifest", "msvcr90.dll"] - for f in files: - path = os.path.join(dir, f) - kw = {'src':path} - if f.endswith('.dll'): - kw['version'] = installer.FileVersion(path, 0) - kw['language'] = installer.FileVersion(path, 1) - result.append((f, kw)) - return result + # At least for VS2010, manifests are no longer provided + name = "msvcr100.dll" + path = os.path.join(dir, name) + kw = {'src':path} + kw['version'] = installer.FileVersion(path, 0) + kw['language'] = installer.FileVersion(path, 1) + return name, kw def generate_license(): import shutil, glob @@ -889,7 +912,7 @@ def generate_license(): dirs = glob.glob(srcdir+"/../"+pat) if not dirs: raise ValueError, "Could not find "+srcdir+"/../"+pat - if len(dirs) > 2: + if len(dirs) > 2 and not snapshot: raise ValueError, "Multiple copies of "+pat dir = dirs[0] shutil.copyfileobj(open(os.path.join(dir, file)), out) @@ -904,16 +927,28 @@ class PyDirectory(Directory): kw['componentflags'] = 2 #msidbComponentAttributesOptional Directory.__init__(self, *args, **kw) - def check_unpackaged(self): - self.unpackaged_files.discard('__pycache__') - self.unpackaged_files.discard('.svn') - if self.unpackaged_files: - print "Warning: Unpackaged files in %s" % self.absolute - print self.unpackaged_files +def hgmanifest(): + # Fetch file list from Mercurial + process = subprocess.Popen(['hg', 'manifest'], stdout=subprocess.PIPE) + stdout, stderr = process.communicate() + # Create nested directories for file tree + result = {} + for line in stdout.splitlines(): + components = line.split('/') + d = result + while len(components) > 1: + d1 = d.setdefault(components[0], {}) + d = d1 + del components[0] + d[components[0]] = None + return result + # See "File Table", "Component Table", "Directory Table", # "FeatureComponents Table" def add_files(db): + installer = msilib.MakeInstaller() + hgfiles = hgmanifest() cab = CAB("python") tmpfiles = [] # Add all executables, icons, text files into the TARGETDIR component @@ -932,11 +967,32 @@ def add_files(db): # msidbComponentAttributesSharedDllRefCount = 8, see "Component Table" dlldir = PyDirectory(db, cab, root, srcdir, "DLLDIR", ".") + launcherdir = PyDirectory(db, cab, root, srcdir, "LAUNCHERDIR", ".") + + # msidbComponentAttributes64bit = 256; this disables registry redirection + # to allow setting the SharedDLLs key in the 64-bit portion even for a + # 32-bit installer. + # XXX does this still allow to install the component on a 32-bit system? + # Pick up 32-bit binary always + launchersrc = PCBUILD + if launchersrc.lower() == 'pcbuild\\x64-pgo': + launchersrc = 'PCBuild\\win32-pgo' + if launchersrc.lower() == 'pcbuild\\amd64': + launchersrc = 'PCBuild' + launcher = os.path.join(srcdir, launchersrc, "py.exe") + launcherdir.start_component("launcher", flags = 8+256, keyfile="py.exe") + launcherdir.add_file(launcher, + version=installer.FileVersion(launcher, 0), + language=installer.FileVersion(launcher, 1)) + launcherw = os.path.join(srcdir, launchersrc, "pyw.exe") + launcherdir.start_component("launcherw", flags = 8+256, keyfile="pyw.exe") + launcherdir.add_file(launcherw, + version=installer.FileVersion(launcherw, 0), + language=installer.FileVersion(launcherw, 1)) pydll = "python%s%s.dll" % (major, minor) pydllsrc = os.path.join(srcdir, PCBUILD, pydll) dlldir.start_component("DLLDIR", flags = 8, keyfile = pydll, uuid = pythondll_uuid) - installer = msilib.MakeInstaller() pyversion = installer.FileVersion(pydllsrc, 0) if not snapshot: # For releases, the Python DLL has the same version as the @@ -952,9 +1008,8 @@ def add_files(db): # pointing to the root directory root.start_component("msvcr90", feature=private_crt) # Results are ID,keyword pairs - manifest, crtdll = extract_msvcr90() - root.add_file(manifest[0], **manifest[1]) - root.add_file(crtdll[0], **crtdll[1]) + crtdll, kwds = extract_msvcr100() + root.add_file(crtdll, **kwds) # Copy the manifest # Actually, don't do that anymore - no DLL in DLLs should have a manifest # dependency on msvcr90.dll anymore, so this should not be necessary @@ -975,104 +1030,40 @@ def add_files(db): # Add all .py files in Lib, except tkinter, test dirs = [] - pydirs = [(root,"Lib")] + pydirs = [(root, "Lib", hgfiles["Lib"], default_feature)] while pydirs: # Commit every now and then, or else installer will complain db.Commit() - parent, dir = pydirs.pop() - if dir == ".svn" or dir == '__pycache__' or dir.startswith("plat-"): + parent, dir, files, feature = pydirs.pop() + if dir.startswith("plat-"): continue - elif dir in ["tkinter", "idlelib", "Icons"]: + if dir in ["tkinter", "idlelib", "turtledemo"]: if not have_tcl: continue + feature = tcltk tcltk.set_current() - elif dir in ['test', 'tests', 'data', 'output']: - # test: Lib, Lib/email, Lib/ctypes, Lib/sqlite3 - # tests: Lib/distutils - # data: Lib/email/test - # output: Lib/test - testsuite.set_current() + elif dir in ('test', 'tests'): + feature = testsuite elif not have_ctypes and dir == "ctypes": continue - else: - default_feature.set_current() + feature.set_current() lib = PyDirectory(db, cab, parent, dir, dir, "%s|%s" % (parent.make_short(dir), dir)) - # Add additional files dirs.append(lib) - lib.glob("*.txt") - if dir=='site-packages': - lib.add_file("README.txt", src="README") - continue - files = lib.glob("*.py") - files += lib.glob("*.pyw") - if files: - # Add an entry to the RemoveFile table to remove bytecode files. - lib.remove_pyc() - # package READMEs if present - lib.glob("README") - if dir=='Lib': - lib.add_file('wsgiref.egg-info') - if dir=='test' and parent.physical=='Lib': - lib.add_file("185test.db") - lib.add_file("audiotest.au") - lib.add_file("sgml_input.html") - lib.add_file("testtar.tar") - lib.add_file("test_difflib_expect.html") - lib.add_file("check_soundcard.vbs") - lib.add_file("empty.vbs") - lib.add_file("Sine-1000Hz-300ms.aif") - lib.add_file("mime.types") - lib.glob("*.uue") - lib.glob("*.pem") - lib.glob("*.pck") - lib.glob("cfgparser.*") - lib.add_file("zip_cp437_header.zip") - lib.add_file("zipdir.zip") - if dir=='capath': - lib.glob("*.0") - if dir=='tests' and parent.physical=='distutils': - lib.add_file("Setup.sample") - if dir=='decimaltestdata': - lib.glob("*.decTest") - if dir=='xmltestdata': - lib.glob("*.xml") - lib.add_file("test.xml.out") - if dir=='output': - lib.glob("test_*") - if dir=='sndhdrdata': - lib.glob("sndhdr.*") - if dir=='idlelib': - lib.glob("*.def") - lib.add_file("idle.bat") - lib.add_file("ChangeLog") - if dir=="Icons": - lib.glob("*.gif") - lib.add_file("idle.icns") - if dir=="command" and parent.physical=="distutils": - lib.glob("wininst*.exe") - lib.add_file("command_template") - if dir=="lib2to3": - lib.removefile("pickle", "*.pickle") - if dir=="macholib": - lib.add_file("README.ctypes") - lib.glob("fetch_macholib*") - if dir=='turtledemo': - lib.add_file("turtle.cfg") - if dir=="pydoc_data": - lib.add_file("_pydoc.css") - if dir=="data" and parent.physical=="test" and parent.basedir.physical=="email": - # This should contain all non-.svn files listed in subversion - for f in os.listdir(lib.absolute): - if f.endswith(".txt") or f==".svn":continue - if f.endswith(".au") or f.endswith(".gif"): - lib.add_file(f) + has_py = False + for name, subdir in files.items(): + if subdir is None: + assert os.path.isfile(os.path.join(lib.absolute, name)) + if name == 'README': + lib.add_file("README.txt", src="README") else: - print("WARNING: New file %s in email/test/data" % f) - for f in os.listdir(lib.absolute): - if os.path.isdir(os.path.join(lib.absolute, f)): - pydirs.append((lib, f)) - for d in dirs: - d.check_unpackaged() + lib.add_file(name) + has_py = has_py or name.endswith(".py") or name.endswith(".pyw") + else: + assert os.path.isdir(os.path.join(lib.absolute, name)) + pydirs.append((lib, name, subdir, feature)) + + if has_py: + lib.remove_pyc() # Add DLLs default_feature.set_current() lib = DLLs @@ -1159,6 +1150,8 @@ def add_files(db): lib.add_file("README.txt", src="README") if f == 'Scripts': lib.add_file("2to3.py", src="2to3") + lib.add_file("pydoc3.py", src="pydoc3") + lib.add_file("pyvenv.py", src="pyvenv") if have_tcl: lib.start_component("pydocgui.pyw", tcltk, keyfile="pydocgui.pyw") lib.add_file("pydocgui.pyw") @@ -1190,6 +1183,8 @@ def add_registry(db): "InstallPath"), ("REGISTRY.doc", msilib.gen_uuid(), "TARGETDIR", registry_component, None, "Documentation"), + ("REGISTRY.path", msilib.gen_uuid(), "TARGETDIR", registry_component, None, + None), ("REGISTRY.def", msilib.gen_uuid(), "TARGETDIR", registry_component, None, None)] + tcldata) # See "FeatureComponents Table". @@ -1206,6 +1201,7 @@ def add_registry(db): add_data(db, "FeatureComponents", [(default_feature.id, "REGISTRY"), (htmlfiles.id, "REGISTRY.doc"), + (prepend_path.id, "REGISTRY.path"), (ext_feature.id, "REGISTRY.def")] + tcldata ) @@ -1244,11 +1240,11 @@ def add_registry(db): "text/plain", "REGISTRY.def"), #Verbs ("py.open", -1, pat % (testprefix, "", "open"), "", - r'"[TARGETDIR]python.exe" "%1" %*', "REGISTRY.def"), + r'"[LAUNCHERDIR]py.exe" "%1" %*', "REGISTRY.def"), ("pyw.open", -1, pat % (testprefix, "NoCon", "open"), "", - r'"[TARGETDIR]pythonw.exe" "%1" %*', "REGISTRY.def"), + r'"[LAUNCHERDIR]pyw.exe" "%1" %*', "REGISTRY.def"), ("pyc.open", -1, pat % (testprefix, "Compiled", "open"), "", - r'"[TARGETDIR]python.exe" "%1" %*', "REGISTRY.def"), + r'"[LAUNCHERDIR]py.exe" "%1" %*', "REGISTRY.def"), ] + tcl_verbs + [ #Icons ("py.icon", -1, pat2 % (testprefix, ""), "", @@ -1347,9 +1343,9 @@ finally: # Merge CRT into MSI file. This requires the database to be closed. mod_dir = os.path.join(os.environ["ProgramFiles"], "Common Files", "Merge Modules") if msilib.Win64: - modules = ["Microsoft_VC90_CRT_x86_x64.msm", "policy_9_0_Microsoft_VC90_CRT_x86_x64.msm"] + modules = ["Microsoft_VC100_CRT_x64.msm"] else: - modules = ["Microsoft_VC90_CRT_x86.msm","policy_9_0_Microsoft_VC90_CRT_x86.msm"] + modules = ["Microsoft_VC100_CRT_x86.msm"] for i, n in enumerate(modules): modules[i] = os.path.join(mod_dir, n) |