diff options
Diffstat (limited to 'Tools/msi')
-rw-r--r-- | Tools/msi/merge.py | 84 | ||||
-rw-r--r-- | Tools/msi/msi.py | 137 | ||||
-rw-r--r-- | Tools/msi/msilib.py | 65 | ||||
-rw-r--r-- | Tools/msi/uuids.py | 47 |
4 files changed, 169 insertions, 164 deletions
diff --git a/Tools/msi/merge.py b/Tools/msi/merge.py deleted file mode 100644 index 85de209a09..0000000000 --- a/Tools/msi/merge.py +++ /dev/null @@ -1,84 +0,0 @@ -import msilib,os,win32com,tempfile,sys -PCBUILD="PCBuild" -certname = None -from config import * - -Win64 = "amd64" in PCBUILD - -mod_dir = os.path.join(os.environ["ProgramFiles"], "Common Files", "Merge Modules") -msi = None -if len(sys.argv)==2: - msi = sys.argv[1] -if Win64: - modules = ["Microsoft_VC90_CRT_x86_x64.msm", "policy_9_0_Microsoft_VC90_CRT_x86_x64.msm"] - if not msi: msi = "python-%s.amd64.msi" % full_current_version -else: - modules = ["Microsoft_VC90_CRT_x86.msm","policy_9_0_Microsoft_VC90_CRT_x86.msm"] - if not msi: msi = "python-%s.msi" % full_current_version -for i, n in enumerate(modules): - modules[i] = os.path.join(mod_dir, n) - -def merge(msi, feature, rootdir, modules): - cab_and_filecount = [] - # Step 1: Merge databases, extract cabfiles - m = msilib.MakeMerge2() - m.OpenLog("merge.log") - print "Opened Log" - m.OpenDatabase(msi) - print "Opened DB" - for module in modules: - print module - m.OpenModule(module,0) - print "Opened Module",module - m.Merge(feature, rootdir) - print "Errors:" - for e in m.Errors: - print e.Type, e.ModuleTable, e.DatabaseTable - print " Modkeys:", - for s in e.ModuleKeys: print s, - print - print " DBKeys:", - for s in e.DatabaseKeys: print s, - print - cabname = tempfile.mktemp(suffix=".cab") - m.ExtractCAB(cabname) - cab_and_filecount.append((cabname, len(m.ModuleFiles))) - m.CloseModule() - m.CloseDatabase(True) - m.CloseLog() - - # Step 2: Add CAB files - i = msilib.MakeInstaller() - db = i.OpenDatabase(msi, win32com.client.constants.msiOpenDatabaseModeTransact) - - v = db.OpenView("SELECT LastSequence FROM Media") - v.Execute(None) - maxmedia = -1 - while 1: - r = v.Fetch() - if not r: break - seq = r.IntegerData(1) - if seq > maxmedia: - maxmedia = seq - print "Start of Media", maxmedia - - for cabname, count in cab_and_filecount: - stream = "merged%d" % maxmedia - msilib.add_data(db, "Media", - [(maxmedia+1, maxmedia+count, None, "#"+stream, None, None)]) - msilib.add_stream(db, stream, cabname) - os.unlink(cabname) - maxmedia += count - # The merge module sets ALLUSERS to 1 in the property table. - # This is undesired; delete that - v = db.OpenView("DELETE FROM Property WHERE Property='ALLUSERS'") - v.Execute(None) - v.Close() - db.Commit() - -merge(msi, "SharedCRT", "TARGETDIR", modules) - -# certname (from config.py) should be (a substring of) -# the certificate subject, e.g. "Python Software Foundation" -if certname: - os.system('signtool sign /n "%s" /t http://timestamp.verisign.com/scripts/timestamp.dll %s' % (certname, msi)) diff --git a/Tools/msi/msi.py b/Tools/msi/msi.py index b668c7aaec..53e652d57f 100644 --- a/Tools/msi/msi.py +++ b/Tools/msi/msi.py @@ -7,6 +7,7 @@ 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,6 +29,8 @@ have_tcl = True PCBUILD="PCbuild" # msvcrt version MSVCR = "90" +# Name of certificate in default store to sign MSI with +certname = None # Make a zip file containing the PDB files for this build? pdbzip = True @@ -115,6 +118,7 @@ pythondll_uuid = { "27":"{4fe21c76-1760-437b-a2f2-99909130a175}", "30":"{6953bc3b-6768-4291-8410-7914ce6e2ca8}", "31":"{4afcba0b-13e4-47c3-bebe-477428b46913}", + "32":"{3ff95315-1096-4d31-bd86-601d5438ad5e}", } [major+minor] # Compute the name that Sphinx gives to the docfile @@ -221,7 +225,8 @@ def build_database(): # schema represents the installer 2.0 database schema. # sequence is the set of standard sequences # (ui/execute, admin/advt/install) - db = msilib.init_database("python-%s%s.msi" % (full_current_version, msilib.arch_ext), + msiname = "python-%s%s.msi" % (full_current_version, msilib.arch_ext) + db = msilib.init_database(msiname, schema, ProductName="Python "+full_current_version+productsuffix, ProductCode=product_code, ProductVersion=current_version, @@ -244,7 +249,7 @@ def build_database(): ("ProductLine", "Python%s%s" % (major, minor)), ]) db.Commit() - return db + return db, msiname def remove_old_versions(db): "Fill the upgrade table." @@ -876,7 +881,6 @@ def generate_license(): shutil.copyfileobj(open(os.path.join(srcdir, "LICENSE")), out) shutil.copyfileobj(open("crtlicense.txt"), out) for name, pat, file in (("bzip2","bzip2-*", "LICENSE"), - ("Berkeley DB", "db-*", "LICENSE"), ("openssl", "openssl-*", "LICENSE"), ("Tcl", "tcl8*", "license.terms"), ("Tk", "tk8*", "license.terms"), @@ -900,6 +904,13 @@ 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 + # See "File Table", "Component Table", "Directory Table", # "FeatureComponents Table" def add_files(db): @@ -963,13 +974,13 @@ def add_files(db): extensions.remove("_ctypes.pyd") # Add all .py files in Lib, except tkinter, test - dirs={} + dirs = [] pydirs = [(root,"Lib")] while pydirs: # Commit every now and then, or else installer will complain db.Commit() parent, dir = pydirs.pop() - if dir == ".svn" or dir.startswith("plat-"): + if dir == ".svn" or dir == '__pycache__' or dir.startswith("plat-"): continue elif dir in ["tkinter", "idlelib", "Icons"]: if not have_tcl: @@ -987,7 +998,7 @@ def add_files(db): default_feature.set_current() lib = PyDirectory(db, cab, parent, dir, dir, "%s|%s" % (parent.make_short(dir), dir)) # Add additional files - dirs[dir]=lib + dirs.append(lib) lib.glob("*.txt") if dir=='site-packages': lib.add_file("README.txt", src="README") @@ -997,19 +1008,14 @@ def add_files(db): if files: # Add an entry to the RemoveFile table to remove bytecode files. lib.remove_pyc() - if dir.endswith('.egg-info'): - lib.add_file('entry_points.txt') - lib.add_file('PKG-INFO') - lib.add_file('top_level.txt') - lib.add_file('zip-safe') - continue + # 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("cfgparser.1") lib.add_file("sgml_input.html") - lib.add_file("test.xml") - lib.add_file("test.xml.out") lib.add_file("testtar.tar") lib.add_file("test_difflib_expect.html") lib.add_file("check_soundcard.vbs") @@ -1018,26 +1024,41 @@ def add_files(db): 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") - if dir=="setuptools": - lib.add_file("cli.exe") - lib.add_file("gui.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): @@ -1049,6 +1070,8 @@ def add_files(db): 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() # Add DLLs default_feature.set_current() lib = DLLs @@ -1064,6 +1087,7 @@ def add_files(db): continue dlls.append(f) lib.add_file(f) + lib.add_file('python3.dll') # Add sqlite if msilib.msi_type=="Intel64;1033": sqlite_arch = "/ia64" @@ -1100,6 +1124,7 @@ def add_files(db): for f in dlls: lib.add_file(f.replace('pyd','lib')) lib.add_file('python%s%s.lib' % (major, minor)) + lib.add_file('python3.lib') # Add the mingw-format library if have_mingw: lib.add_file('libpython%s%s.a' % (major, minor)) @@ -1120,7 +1145,7 @@ def add_files(db): # Add tools tools.set_current() tooldir = PyDirectory(db, cab, root, "Tools", "Tools", "TOOLS|Tools") - for f in ['i18n', 'pynche', 'Scripts', 'webchecker']: + for f in ['i18n', 'pynche', 'Scripts']: lib = PyDirectory(db, cab, tooldir, f, f, "%s|%s" % (tooldir.make_short(f), f)) lib.glob("*.py") lib.glob("*.pyw", exclude=['pydocgui.pyw']) @@ -1307,7 +1332,7 @@ def build_pdbzip(): pdbzip.write(os.path.join(srcdir, PCBUILD, f), f) pdbzip.close() -db = build_database() +db,msiname = build_database() try: add_features(db) add_ui(db) @@ -1318,5 +1343,77 @@ try: finally: del db +# 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"] +else: + modules = ["Microsoft_VC90_CRT_x86.msm","policy_9_0_Microsoft_VC90_CRT_x86.msm"] + +for i, n in enumerate(modules): + modules[i] = os.path.join(mod_dir, n) + +def merge(msi, feature, rootdir, modules): + cab_and_filecount = [] + # Step 1: Merge databases, extract cabfiles + m = msilib.MakeMerge2() + m.OpenLog("merge.log") + m.OpenDatabase(msi) + for module in modules: + print module + m.OpenModule(module,0) + m.Merge(feature, rootdir) + print "Errors:" + for e in m.Errors: + print e.Type, e.ModuleTable, e.DatabaseTable + print " Modkeys:", + for s in e.ModuleKeys: print s, + print + print " DBKeys:", + for s in e.DatabaseKeys: print s, + print + cabname = tempfile.mktemp(suffix=".cab") + m.ExtractCAB(cabname) + cab_and_filecount.append((cabname, len(m.ModuleFiles))) + m.CloseModule() + m.CloseDatabase(True) + m.CloseLog() + + # Step 2: Add CAB files + i = msilib.MakeInstaller() + db = i.OpenDatabase(msi, constants.msiOpenDatabaseModeTransact) + + v = db.OpenView("SELECT LastSequence FROM Media") + v.Execute(None) + maxmedia = -1 + while 1: + r = v.Fetch() + if not r: break + seq = r.IntegerData(1) + if seq > maxmedia: + maxmedia = seq + print "Start of Media", maxmedia + + for cabname, count in cab_and_filecount: + stream = "merged%d" % maxmedia + msilib.add_data(db, "Media", + [(maxmedia+1, maxmedia+count, None, "#"+stream, None, None)]) + msilib.add_stream(db, stream, cabname) + os.unlink(cabname) + maxmedia += count + # The merge module sets ALLUSERS to 1 in the property table. + # This is undesired; delete that + v = db.OpenView("DELETE FROM Property WHERE Property='ALLUSERS'") + v.Execute(None) + v.Close() + db.Commit() + +merge(msiname, "SharedCRT", "TARGETDIR", modules) + +# certname (from config.py) should be (a substring of) +# the certificate subject, e.g. "Python Software Foundation" +if certname: + os.system('signtool sign /n "%s" /t http://timestamp.verisign.com/scripts/timestamp.dll %s' % (certname, msiname)) + if pdbzip: build_pdbzip() diff --git a/Tools/msi/msilib.py b/Tools/msi/msilib.py index 6f49b4cf8c..5795d0ec6b 100644 --- a/Tools/msi/msilib.py +++ b/Tools/msi/msilib.py @@ -5,7 +5,7 @@ import win32com.client.gencache import win32com.client import pythoncom, pywintypes from win32com.client import constants -import re, string, os, sets, glob, subprocess, sys, _winreg, struct +import re, string, os, sets, glob, subprocess, sys, _winreg, struct, _msi try: basestring @@ -350,7 +350,7 @@ def gen_uuid(): class CAB: def __init__(self, name): self.name = name - self.file = open(name+".txt", "wt") + self.files = [] self.filenames = sets.Set() self.index = 0 @@ -369,51 +369,18 @@ class CAB: if not logical: logical = self.gen_id(dir, file) self.index += 1 - if full.find(" ")!=-1: - print >>self.file, '"%s" %s' % (full, logical) - else: - print >>self.file, '%s %s' % (full, logical) + self.files.append((full, logical)) return self.index, logical def commit(self, db): - self.file.close() try: os.unlink(self.name+".cab") except OSError: pass - for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"), - (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"), - (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"), - (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"), - ]: - try: - key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, k) - dir = _winreg.QueryValueEx(key, v)[0] - _winreg.CloseKey(key) - except (WindowsError, IndexError): - continue - cabarc = os.path.join(dir, r"Bin", "cabarc.exe") - if not os.path.exists(cabarc): - continue - break - else: - print "WARNING: cabarc.exe not found in registry" - cabarc = "cabarc.exe" - cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name) - p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - for line in p.stdout: - if line.startswith(" -- adding "): - sys.stdout.write(".") - else: - sys.stdout.write(line) - sys.stdout.flush() - if not os.path.exists(self.name+".cab"): - raise IOError, "cabarc failed" + _msi.FCICreate(self.name+".cab", self.files) add_data(db, "Media", [(1, self.index, None, "#"+self.name, None, None)]) add_stream(db, self.name, self.name+".cab") - os.unlink(self.name+".txt") os.unlink(self.name+".cab") db.Commit() @@ -451,6 +418,12 @@ class Directory: else: self.absolute = physical blogical = None + # initially assume that all files in this directory are unpackaged + # as files from self.absolute get added, this set is reduced + self.unpackaged_files = set() + for f in os.listdir(self.absolute): + if os.path.isfile(os.path.join(self.absolute, f)): + self.unpackaged_files.add(f) add_data(db, "Directory", [(logical, blogical, default)]) def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None): @@ -527,6 +500,11 @@ class Directory: src = file file = os.path.basename(file) absolute = os.path.join(self.absolute, src) + if absolute.startswith(self.absolute): + # mark file as packaged + relative = absolute[len(self.absolute)+1:] + if relative in self.unpackaged_files: + self.unpackaged_files.remove(relative) assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names if self.keyfiles.has_key(file): logical = self.keyfiles[file] @@ -572,10 +550,17 @@ class Directory: return files def remove_pyc(self): - "Remove .pyc/.pyo files on uninstall" + "Remove .pyc/.pyo files from __pycache__ on uninstall" + directory = self.logical + "_pycache" + add_data(self.db, "Directory", [(directory, self.logical, "__PYCA~1|__pycache__")]) + flags = 256 if Win64 else 0 + add_data(self.db, "Component", + [(directory, gen_uuid(), directory, flags, None, None)]) + add_data(self.db, "FeatureComponents", [(current_feature.id, directory)]) + add_data(self.db, "CreateFolder", [(directory, directory)]) add_data(self.db, "RemoveFile", - [(self.component+"c", self.component, "*.pyc", self.logical, 2), - (self.component+"o", self.component, "*.pyo", self.logical, 2)]) + [(self.component, self.component, "*.*", directory, 2), + ]) def removefile(self, key, pattern): "Add a RemoveFile entry" diff --git a/Tools/msi/uuids.py b/Tools/msi/uuids.py index a5e5cd21f0..09da40accf 100644 --- a/Tools/msi/uuids.py +++ b/Tools/msi/uuids.py @@ -7,23 +7,6 @@ # generated. For official releases, we record the product codes, # so people can refer to them. product_codes = { - '2.4.101': '{0e9b4d8e-6cda-446e-a208-7b92f3ddffa0}', # 2.4a1, released as a snapshot - '2.4.102': '{1b998745-4901-4edb-bc52-213689e1b922}', # 2.4a2 - '2.4.103': '{33fc8bd2-1e8f-4add-a40a-ade2728d5942}', # 2.4a3 - '2.4.111': '{51a7e2a8-2025-4ef0-86ff-e6aab742d1fa}', # 2.4b1 - '2.4.112': '{4a5e7c1d-c659-4fe3-b8c9-7c65bd9c95a5}', # 2.4b2 - '2.4.121': '{75508821-a8e9-40a8-95bd-dbe6033ddbea}', # 2.4c1 - '2.4.122': '{83a9118b-4bdd-473b-afc3-bcb142feca9e}', # 2.4c2 - '2.4.150': '{82d9302e-f209-4805-b548-52087047483a}', # 2.4.0 - '2.4.1121':'{be027411-8e6b-4440-a29b-b07df0690230}', # 2.4.1c1 - '2.4.1122':'{02818752-48bf-4074-a281-7a4114c4f1b1}', # 2.4.1c2 - '2.4.1150':'{4d4f5346-7e4a-40b5-9387-fdb6181357fc}', # 2.4.1 - '2.4.2121':'{5ef9d6b6-df78-45d2-ab09-14786a3c5a99}', # 2.4.2c1 - '2.4.2150':'{b191e49c-ea23-43b2-b28a-14e0784069b8}', # 2.4.2 - '2.4.3121':'{f669ed4d-1dce-41c4-9617-d985397187a1}', # 2.4.3c1 - '2.4.3150':'{75e71add-042c-4f30-bfac-a9ec42351313}', # 2.4.3 - '2.4.4121':'{cd2862db-22a4-4688-8772-85407ea21550}', # 2.4.4c1 - '2.4.4150':'{60e2c8c9-6cf3-4b1a-9618-e304946c94e6}', # 2.4.4 '2.5.101': '{bc14ce3e-5e72-4a64-ac1f-bf59a571898c}', # 2.5a1 '2.5.102': '{5eed51c1-8e9d-4071-94c5-b40de5d49ba5}', # 2.5a2 '2.5.103': '{73dcd966-ffec-415f-bb39-8342c1f47017}', # 2.5a3 @@ -50,6 +33,23 @@ product_codes = { '2.6.1150':'{9cc89170-000b-457d-91f1-53691f85b223}', # 2.6.1 '2.6.2121':'{adac412b-b209-4c15-b6ab-dca1b6e47144}', # 2.6.2c1 '2.6.2150':'{24aab420-4e30-4496-9739-3e216f3de6ae}', # 2.6.2 + '2.6.3121':'{a73e0254-dcda-4fe4-bf37-c7e1c4f4ebb6}', # 2.6.3c1 + '2.6.3150':'{3d9ac095-e115-4e94-bdef-7f7edf17697d}', # 2.6.3 + '2.6.4121':'{727de605-0359-4606-a94b-c2033652379b}', # 2.6.4c1 + '2.6.4122':'{4f7603c6-6352-4299-a398-150a31b19acc}', # 2.6.4c2 + '2.6.4150':'{e7394a0f-3f80-45b1-87fc-abcd51893246}', # 2.6.4 + '2.6.5121':'{e0e273d7-7598-4701-8325-c90c069fd5ff}', # 2.6.5c1 + '2.6.5122':'{fa227b76-0671-4dc6-b826-c2ff2a70dfd5}', # 2.6.5c2 + '2.6.5150':'{4723f199-fa64-4233-8e6e-9fccc95a18ee}', # 2.6.5 + '2.7.101': '{eca1bbef-432c-49ae-a667-c213cc7bbf22}', # 2.7a1 + '2.7.102': '{21ce16ed-73c4-460d-9b11-522f417b2090}', # 2.7a2 + '2.7.103': '{6e7dbd55-ba4a-48ac-a688-6c75db4d7500}', # 2.7a3 + '2.7.104': '{ee774ba3-74a5-48d9-b425-b35a287260c8}', # 2.7a4 + '2.7.111': '{9cfd9ec7-a9c7-4980-a1c6-054fc6493eb3}', # 2.7b1 + '2.7.112': '{9a72faf6-c304-4165-8595-9291ff30cac6}', # 2.7b2 + '2.7.121': '{f530c94a-dd53-4de9-948e-b632b9cb48d2}', # 2.7c1 + '2.7.122': '{f80905d2-dd8d-4b8e-8a40-c23c93dca07d}', # 2.7c2 + '2.7.150': '{20c31435-2a0a-4580-be8b-ac06fc243ca4}', # 2.7.0 '3.0.101': '{8554263a-3242-4857-9359-aa87bc2c58c2}', # 3.0a1 '3.0.102': '{692d6e2c-f0ac-40b8-a133-7191aeeb67f9}', # 3.0a2 '3.0.103': '{49cb2995-751a-4753-be7a-d0b1bb585e06}', # 3.0a3 @@ -77,7 +77,14 @@ product_codes = { '3.1.1150':'{7ff90460-89b7-435b-b583-b37b2815ccc7}', # 3.1.1 '3.1.2121':'{ec45624a-378c-43be-91f3-3f7a59b0d90c}', # 3.1.2c1 '3.1.2150':'{d40af016-506c-43fb-a738-bd54fa8c1e85}', # 3.1.2 - '3.1.3121':'{a1e436f8-92fc-4ddb-af18-a12529c57aaf}', # 3.1.3rc1 - '3.1.3122':'{2fc19026-a3e5-493e-92a0-c1f3b4a272ae}', # 3.1.3rc2 - '3.1.3150':'{f719d8a6-46fc-4d71-94c6-ffd17a8c9f35}', # 3.1.3 + '3.2.101' :'{b411f168-7a36-4fff-902c-a554d1c78a4f}', # 3.2a1 + '3.2.102' :'{79ff73b7-8359-410f-b9c5-152d2026f8c8}', # 3.2a2 + '3.2.103' :'{e7635c65-c221-4b9b-b70a-5611b8369d77}', # 3.2a3 + '3.2.104' :'{748cd139-75b8-4ca8-98a7-58262298181e}', # 3.2a4 + '3.2.111' :'{20bfc16f-c7cd-4fc0-8f96-9914614a3c50}', # 3.2b1 + '3.2.112' :'{0e350c98-8d73-4993-b686-cfe87160046e}', # 3.2b2 + '3.2.121' :'{2094968d-7583-47f6-a7fd-22304532e09f}', # 3.2rc1 + '3.2.122' :'{4f3edfa6-cf70-469a-825f-e1206aa7f412}', # 3.2rc2 + '3.2.123' :'{90c673d7-8cfd-4969-9816-f7d70bad87f3}', # 3.2rc3 + '3.2.150' :'{b2042d5e-986d-44ec-aee3-afe4108ccc93}', # 3.2.0 } |