123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- # Copyright (C) 2001-2006 William Joseph.
- #
- # This file is part of GtkRadiant.
- #
- # GtkRadiant is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # GtkRadiant is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with GtkRadiant; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- import os.path
- import xml.dom
- import os
- import stat
- import string
- from xml.dom.minidom import parse
- import msi
- cwd = os.getcwd()
- print("cwd=" + cwd)
- def format_guid(guid):
- return "{" + guid.upper() + "}"
- def generate_guid():
- os.system("uuidgen > tmp_uuid.txt")
- uuidFile = file("tmp_uuid.txt", "rt")
- guid = format_guid(uuidFile.read(36))
- uuidFile.close()
- os.system("del tmp_uuid.txt")
- return guid
-
- def path_components(path):
- directories = []
- remaining = path
- while(remaining != ""):
- splitPath = os.path.split(remaining)
- remaining = splitPath[0]
- directories.append(splitPath[1])
- directories.reverse()
- return directories
-
- class Feature:
- def __init__(self, feature, parent, title, desc, display, level, directory, attributes):
- self.feature = feature
- self.parent = parent
- self.title = title
- self.desc = desc
- self.display = display
- self.level = level
- self.directory = directory
- self.attributes = attributes
- class FeatureComponent:
- def __init__(self, feature, component):
- self.feature = feature
- self.component = component
-
- class Directory:
- def __init__(self, directory, parent, default):
- self.directory = directory
- self.parent = parent
- self.default = default
-
- class Component:
- def __init__(self, name, keypath, directory, attributes):
- self.name = name
- self.keypath = keypath
- self.directory = directory
- self.attributes = attributes
-
- class File:
- def __init__(self, file, component, filename, filesize, sequence):
- self.file = file
- self.component = component
- self.filename = filename
- self.filesize = filesize
- self.sequence = sequence
-
- class Shortcut:
- def __init__(self, name, directory, component, feature, icon):
- self.name = name
- self.directory = directory
- self.component = component
- self.feature = feature
- self.icon = icon
- class ComponentFiles:
- def __init__(self, name, files, directory):
- self.name = name
- self.files = files
- self.directory = directory
- class MSIPackage:
- def __init__(self, packageFile):
- self.code = ""
- self.name = ""
- self.version = ""
- self.target = ""
- self.license = ""
- self.cabList = []
- self.featureCount = 0
- self.featureTable = []
- self.featurecomponentsTable = []
- self.componentCache = {}
- self.componentCount = 0
- self.componentTable = {}
- self.directoryTree = {}
- self.directoryCount = 0
- self.directoryTable = []
- self.fileCount = 0
- self.fileTable = []
- self.shortcutCount = 0
- self.shortcutTable = []
- self.createPackage(packageFile)
-
- def addDirectory(self, directoryName, parentKey, directory):
- if(not directory.has_key(directoryName)):
- directoryKey = "d" + str(self.directoryCount)
- self.directoryCount = self.directoryCount + 1
- print("adding msi directory " + directoryKey + " parent=" + parentKey + " name=" + directoryName)
- self.directoryTable.append(Directory(directoryKey, parentKey, directoryKey + "|" + directoryName))
- directory[directoryName] = (directoryKey, {})
- else:
- print("ignored duplicate directory " + directoryName)
- return directory[directoryName]
-
- def parseComponentTree(self, treeElement, parent, directory, directoryPath, component):
- files = []
- for childElement in treeElement.childNodes:
- if (childElement.nodeName == "file"):
- fileName = childElement.getAttribute("name")
- filePath = os.path.join(directoryPath, fileName)
- if(fileName != "" and os.path.exists(filePath)):
- print("found file " + filePath)
- file = (fileName, os.path.getsize(filePath), filePath)
- files.append(file)
- else:
- raise Exception("file not found " + filePath)
- if (childElement.nodeName == "dir"):
- directoryName = childElement.getAttribute("name")
- print("found directory " + directoryName)
- directoryPair = self.addDirectory(directoryName, parent, directory)
- self.parseComponentTree(childElement, directoryPair[0], directoryPair[1], os.path.join(directoryPath, directoryName), component)
-
- count = len(files)
- if(count != 0):
- componentKey = "c" + str(self.componentCount)
- self.componentCount = self.componentCount + 1
- msiComponent = ComponentFiles(componentKey, files, parent);
- print("adding msi component " + msiComponent.name + " with " + str(count) + " file(s)")
- component.append(msiComponent)
-
- def parseComponent(self, componentElement, rootPath):
- shortcut = componentElement.getAttribute("shortcut")
- icon = componentElement.getAttribute("icon")
- component = []
- subDirectory = componentElement.getAttribute("subdirectory")
- directoryPair = ("TARGETDIR", self.directoryTree)
- for directoryName in path_components(subDirectory):
- directoryPair = self.addDirectory(directoryName, directoryPair[0], directoryPair[1])
- self.parseComponentTree(componentElement, directoryPair[0], directoryPair[1], rootPath, component)
- component.reverse()
- print("component requires " + str(len(component)) + " msi component(s)")
- return (component, shortcut, icon)
-
- def parseComponentXML(self, filename, rootPath):
- componentDocument = parse(filename)
- print("parsing component file " + filename)
- componentElement = componentDocument.documentElement
- return self.parseComponent(componentElement, rootPath)
-
- def componentForName(self, name, rootPath):
- if(self.componentCache.has_key(name)):
- return self.componentCache[name]
- else:
- component = self.parseComponentXML(name, rootPath)
- self.componentCache[name] = component
- return component
-
- def parseFeature(self, featureElement, parent, index):
- featureName = "ft" + str(self.featureCount)
- self.featureCount = self.featureCount + 1
- title = featureElement.getAttribute("name")
- desc = featureElement.getAttribute("desc")
- print("adding msi feature " + featureName + " title=" + title)
- feature = Feature(featureName, parent, title, desc, index, 1, "TARGETDIR", 8)
- self.featureTable.append(feature)
- featureComponents = {}
- indexChild = 2
- for childElement in featureElement.childNodes:
- if (childElement.nodeName == "feature"):
- self.parseFeature(childElement, featureName, indexChild)
- indexChild = indexChild + 2
- elif (childElement.nodeName == "component"):
- componentName = os.path.normpath(os.path.join(cwd, childElement.getAttribute("name")))
- if(featureComponents.has_key(componentName)):
- raise Exception("feature \"" + title + "\" contains more than one reference to \"" + componentName + "\"")
- featureComponents[componentName] = ""
- componentSource = os.path.normpath(childElement.getAttribute("root"))
- print("found component reference " + componentName)
- componentPair = self.componentForName(componentName, componentSource)
- component = componentPair[0]
- for msiComponent in component:
- print("adding msi featurecomponent " + featureName + " name=" + msiComponent.name)
- self.featurecomponentsTable.append(FeatureComponent(featureName, msiComponent.name))
- if(not self.componentTable.has_key(msiComponent.name)):
- keyPath = ""
- for fileTuple in msiComponent.files:
- fileKey = "f" + str(self.fileCount)
- self.fileCount = self.fileCount + 1
- if(keyPath == ""):
- keyPath = fileKey
- print("component " + msiComponent.name + " keypath=" + keyPath)
- print("adding msi file " + fileKey + " name=" + fileTuple[0] + " size=" + str(fileTuple[1]))
- self.fileTable.append(File(fileKey, msiComponent.name, fileKey + "|" + fileTuple[0], fileTuple[1], self.fileCount))
- self.cabList.append("\"" + fileTuple[2] + "\" " + fileKey + "\n")
- self.componentTable[msiComponent.name] = Component(msiComponent.name, keyPath, msiComponent.directory, 0)
-
- shortcut = componentPair[1]
- if(shortcut != ""):
- shortcutName = "sc" + str(self.shortcutCount)
- self.shortcutCount = self.shortcutCount + 1
- self.shortcutTable.append(Shortcut(shortcutName + "|" + shortcut, "ProductShortcutFolder", component[0].name, featureName, componentPair[2]))
- print("adding msi shortcut " + shortcut)
- def parsePackage(self, packageElement):
- index = 2
- self.code = packageElement.getAttribute("code")
- if(self.code == ""):
- raise Exception("invalid package code")
- self.version = packageElement.getAttribute("version")
- if(self.version == ""):
- raise Exception("invalid package version")
- self.name = packageElement.getAttribute("name")
- if(self.name == ""):
- raise Exception("invalid package name")
- self.target = packageElement.getAttribute("target")
- if(self.target == ""):
- raise Exception("invalid target directory")
- self.license = packageElement.getAttribute("license")
- if(self.license == ""):
- raise Exception("invalid package license agreement")
- for childElement in packageElement.childNodes:
- if (childElement.nodeName == "feature"):
- self.parseFeature(childElement, "", index)
- index = index + 2
- def parsePackageXML(self, filename):
- document = parse(filename)
- print("parsing package file " + filename)
- self.parsePackage(document.documentElement)
-
- def createPackage(self, packageFile):
- self.directoryTable.append(Directory("TARGETDIR", "", "SourceDir"))
- self.directoryTable.append(Directory("ProgramMenuFolder", "TARGETDIR", "."))
- self.directoryTable.append(Directory("SystemFolder", "TARGETDIR", "."))
- self.parsePackageXML(packageFile)
- if(self.shortcutCount != 0):
- self.directoryTable.append(Directory("ProductShortcutFolder", "ProgramMenuFolder", "s0|" + self.name))
-
- def writeFileTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\ns72\ts72\tl255\ti4\tS72\tS20\tI2\ti2\nFile\tFile\n")
- for row in self.fileTable:
- tableFile.write(row.file + "\t" + row.component + "\t" + row.filename + "\t" + str(row.filesize) + "\t" + "" + "\t" + "" + "\t" + "0" + "\t" + str(row.sequence) + "\n")
-
- def writeComponentTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("Component\tComponentId\tDirectory_\tAttributes\tCondition\tKeyPath\ns72\tS38\ts72\ti2\tS255\tS72\nComponent\tComponent\n")
- for k, row in self.componentTable.iteritems():
- tableFile.write(row.name + "\t" + generate_guid() + "\t" + row.directory + "\t" + str(row.attributes) + "\t" + "" + "\t" + row.keypath + "\n")
-
- def writeFeatureComponentsTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("Feature_\tComponent_\ns38\ts72\nFeatureComponents\tFeature_\tComponent_\n")
- for row in self.featurecomponentsTable:
- tableFile.write(row.feature + "\t" + row.component + "\n")
-
- def writeDirectoryTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("Directory\tDirectory_Parent\tDefaultDir\ns72\tS72\tl255\nDirectory\tDirectory\n")
- for row in self.directoryTable:
- tableFile.write(row.directory + "\t" + row.parent + "\t" + row.default + "\n")
-
- def writeFeatureTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("Feature\tFeature_Parent\tTitle\tDescription\tDisplay\tLevel\tDirectory_\tAttributes\ns38\tS38\tL64\tL255\tI2\ti2\tS72\ti2\nFeature\tFeature\n")
- for row in self.featureTable:
- tableFile.write(row.feature + "\t" + row.parent + "\t" + row.title + "\t" + row.desc + "\t" + str(row.display) + "\t" + str(row.level) + "\t" + row.directory + "\t" + str(row.attributes) + "\n")
- def writeMediaTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("DiskId\tLastSequence\tDiskPrompt\tCabinet\tVolumeLabel\tSource\ni2\ti2\tL64\tS255\tS32\tS72\nMedia\tDiskId\n")
- tableFile.write("1" + "\t" + str(self.fileCount) + "\t" + "" + "\t" + "#archive.cab" + "\t" + "" + "\t" + "" + "\n")
- def writeShortcutTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("Shortcut\tDirectory_\tName\tComponent_\tTarget\tArguments\tDescription\tHotkey\tIcon_\tIconIndex\tShowCmd\tWkDir\ns72\ts72\tl128\ts72\ts72\tS255\tL255\tI2\tS72\tI2\tI2\tS72\nShortcut\tShortcut\n")
- for row in self.shortcutTable:
- tableFile.write(row.component + "\t" + row.directory + "\t" + row.name + "\t" + row.component + "\t" + row.feature + "\t" + "" + "\t" + "" + "\t" + "" + "\t" + row.icon + "\t" + "" + "\t" + "" + "\t" + "" + "\n")
-
- def writeRemoveFileTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("FileKey\tComponent_\tFileName\tDirProperty\tInstallMode\ns72\ts72\tL255\ts72\ti2\nRemoveFile\tFileKey\n")
- count = 0
- for row in self.shortcutTable:
- tableFile.write("rf" + str(count) + "\t" + row.component + "\t" + "" + "\t" + row.directory + "\t" + "2" + "\n")
- count = count + 1
-
- def writeCustomActionTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("Action\tType\tSource\tTarget\ns72\ti2\tS72\tS255\nCustomAction\tAction\n")
- tableFile.write("caSetTargetDir\t51\tTARGETDIR\t" + self.target)
-
- def writeUpgradeTable(self, name):
- tableFile = file(name, "wt")
- tableFile.write("UpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\tRemove\tActionProperty\ns38\tS20\tS20\tS255\ti4\tS255\ts72\nUpgrade\tUpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\n")
- tableFile.write(format_guid(self.code) + "\t\t" + self.version + "\t1033\t1\t\tRELATEDPRODUCTS")
-
- def writeMSILicense(self, msiName, licenseName):
- if(not os.path.exists(licenseName)):
- raise Exception("file not found: " + licenseName)
- print("license=\"" + licenseName + "\"")
- licenseFile = file(licenseName, "rt")
- text = licenseFile.read(1024)
- rtfString = ""
- while(text != ""):
- rtfString += text
- text = licenseFile.read(1024)
- msiDB = msi.Database(msiName)
- msiDB.setlicense(rtfString[:-1])
- msiDB.commit()
- def writeMSIProperties(self, msiName):
- msiDB = msi.Database(msiName)
- print("ProductCode=" + format_guid(self.code))
- msiDB.setproperty("ProductCode", format_guid(self.code))
- print("UpgradeCode=" + format_guid(self.code))
- msiDB.setproperty("UpgradeCode", format_guid(self.code))
- print("ProductName=" + self.name)
- msiDB.setproperty("ProductName", self.name)
- print("ProductVersion=" + self.version)
- msiDB.setproperty("ProductVersion", self.version)
- msiDB.setproperty("RELATEDPRODUCTS", "")
- msiDB.setproperty("SecureCustomProperties", "RELATEDPRODUCTS")
- msiDB.commit()
- def writeMSI(self, msiTemplate, msiName):
- msiWorkName = "working.msi"
- if(os.system("copy " + msiTemplate + " " + msiWorkName) != 0):
- raise Exception("copy failed")
- os.system("msiinfo " + msiWorkName + " /w 2 /v " + generate_guid() + " /a \"Radiant Community\" /j \"" + self.name + "\" /o \"This installation database contains the logic and data needed to install " + self.name + "\"")
- self.writeMSIProperties(msiWorkName)
- self.writeMSILicense(msiWorkName, self.license)
-
- self.writeFileTable("File.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" File.idt")
- os.system("del File.idt")
- self.writeComponentTable("Component.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Component.idt")
- os.system("del Component.idt")
- self.writeFeatureComponentsTable("FeatureComponents.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" FeatureComponents.idt")
- os.system("del FeatureComponents.idt")
- self.writeDirectoryTable("Directory.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Directory.idt")
- os.system("del Directory.idt")
- self.writeFeatureTable("Feature.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Feature.idt")
- os.system("del Feature.idt")
- self.writeMediaTable("Media.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Media.idt")
- os.system("del Media.idt")
- self.writeShortcutTable("Shortcut.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Shortcut.idt")
- os.system("del Shortcut.idt")
- self.writeRemoveFileTable("RemoveFile.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" RemoveFile.idt")
- os.system("del RemoveFile.idt")
- self.writeCustomActionTable("CustomAction.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" CustomAction.idt")
- os.system("del CustomAction.idt")
- self.writeUpgradeTable("Upgrade.idt")
- os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Upgrade.idt")
- os.system("del Upgrade.idt")
- cabText = file("archive_files.txt", "wt")
- for cabDirective in self.cabList:
- cabText.write(cabDirective)
- cabText.close()
- if(os.system("cabarc -m LZX:21 n archive.cab @archive_files.txt") != 0):
- raise Exception("cabarc returned error")
- os.system("del archive_files.txt")
- os.system("msidb -d " + msiWorkName + " -a archive.cab")
- os.system("del archive.cab")
-
- print("running standard MSI validators ...")
- if(os.system("msival2 " + msiWorkName + " darice.cub > darice.txt") != 0):
- raise Exception("MSI VALIDATION ERROR: see darice.txt")
- print("running Logo Program validators ...")
- if(os.system("msival2 " + msiWorkName + " logo.cub > logo.txt") != 0):
- raise Exception("MSI VALIDATION ERROR: see logo.txt")
- print("running XP Logo Program validators ...")
- if(os.system("msival2 " + msiWorkName + " XPlogo.cub > XPlogo.txt") != 0):
- raise Exception("MSI VALIDATION ERROR: see XPlogo.txt")
-
- msiNameQuoted = "\"" + msiName + "\""
- if(os.path.exists(os.path.join(".\\", msiName)) and os.system("del " + msiNameQuoted) != 0):
- raise Exception("failed to delete old target")
- if(os.system("rename " + msiWorkName + " " + msiNameQuoted) != 0):
- raise Exception("failed to rename new target")
-
|