1 # Copyright (C) 2001-2006 William Joseph.
2 # For a list of contributors, see the accompanying CONTRIBUTORS file.
4 # This file is part of GtkRadiant.
6 # GtkRadiant is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # GtkRadiant is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with GtkRadiant; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27 from xml.dom.minidom import parse
35 def format_guid(guid):
36 return "{" + guid.upper() + "}"
39 os.system("uuidgen > tmp_uuid.txt")
40 uuidFile = file("tmp_uuid.txt", "rt")
41 guid = format_guid(uuidFile.read(36))
43 os.system("del tmp_uuid.txt")
46 def path_components(path):
49 while(remaining != ""):
50 splitPath = os.path.split(remaining)
51 remaining = splitPath[0]
52 directories.append(splitPath[1])
59 def __init__(self, feature, parent, title, desc, display, level, directory, attributes):
60 self.feature = feature
64 self.display = display
66 self.directory = directory
67 self.attributes = attributes
69 class FeatureComponent:
70 def __init__(self, feature, component):
71 self.feature = feature
72 self.component = component
75 def __init__(self, directory, parent, default):
76 self.directory = directory
78 self.default = default
81 def __init__(self, name, keypath, directory, attributes):
83 self.keypath = keypath
84 self.directory = directory
85 self.attributes = attributes
88 def __init__(self, file, component, filename, filesize, sequence):
90 self.component = component
91 self.filename = filename
92 self.filesize = filesize
93 self.sequence = sequence
96 def __init__(self, name, directory, component, feature, icon):
98 self.directory = directory
99 self.component = component
100 self.feature = feature
103 class ComponentFiles:
104 def __init__(self, name, files, directory):
107 self.directory = directory
110 def __init__(self, packageFile):
117 self.featureCount = 0
118 self.featureTable = []
119 self.featurecomponentsTable = []
120 self.componentCache = {}
121 self.componentCount = 0
122 self.componentTable = {}
123 self.directoryTree = {}
124 self.directoryCount = 0
125 self.directoryTable = []
128 self.shortcutCount = 0
129 self.shortcutTable = []
130 self.createPackage(packageFile)
132 def addDirectory(self, directoryName, parentKey, directory):
133 if(not directory.has_key(directoryName)):
134 directoryKey = "d" + str(self.directoryCount)
135 self.directoryCount = self.directoryCount + 1
136 print("adding msi directory " + directoryKey + " parent=" + parentKey + " name=" + directoryName)
137 self.directoryTable.append(Directory(directoryKey, parentKey, directoryKey + "|" + directoryName))
138 directory[directoryName] = (directoryKey, {})
140 print("ignored duplicate directory " + directoryName)
141 return directory[directoryName]
143 def parseComponentTree(self, treeElement, parent, directory, directoryPath, component):
145 for childElement in treeElement.childNodes:
146 if (childElement.nodeName == "file"):
147 fileName = childElement.getAttribute("name")
148 filePath = os.path.join(directoryPath, fileName)
149 if(fileName != "" and os.path.exists(filePath)):
150 print("found file " + filePath)
151 file = (fileName, os.path.getsize(filePath), filePath)
154 raise Exception("file not found " + filePath)
156 if (childElement.nodeName == "dir"):
157 directoryName = childElement.getAttribute("name")
158 print("found directory " + directoryName)
159 directoryPair = self.addDirectory(directoryName, parent, directory)
160 self.parseComponentTree(childElement, directoryPair[0], directoryPair[1], os.path.join(directoryPath, directoryName), component)
164 componentKey = "c" + str(self.componentCount)
165 self.componentCount = self.componentCount + 1
166 msiComponent = ComponentFiles(componentKey, files, parent);
167 print("adding msi component " + msiComponent.name + " with " + str(count) + " file(s)")
168 component.append(msiComponent)
170 def parseComponent(self, componentElement, rootPath):
171 shortcut = componentElement.getAttribute("shortcut")
172 icon = componentElement.getAttribute("icon")
174 subDirectory = componentElement.getAttribute("subdirectory")
175 directoryPair = ("TARGETDIR", self.directoryTree)
176 for directoryName in path_components(subDirectory):
177 directoryPair = self.addDirectory(directoryName, directoryPair[0], directoryPair[1])
178 self.parseComponentTree(componentElement, directoryPair[0], directoryPair[1], rootPath, component)
180 print("component requires " + str(len(component)) + " msi component(s)")
181 return (component, shortcut, icon)
183 def parseComponentXML(self, filename, rootPath):
184 componentDocument = parse(filename)
185 print("parsing component file " + filename)
186 componentElement = componentDocument.documentElement
187 return self.parseComponent(componentElement, rootPath)
189 def componentForName(self, name, rootPath):
190 if(self.componentCache.has_key(name)):
191 return self.componentCache[name]
193 component = self.parseComponentXML(name, rootPath)
194 self.componentCache[name] = component
197 def parseFeature(self, featureElement, parent, index):
198 featureName = "ft" + str(self.featureCount)
199 self.featureCount = self.featureCount + 1
200 title = featureElement.getAttribute("name")
201 desc = featureElement.getAttribute("desc")
202 print("adding msi feature " + featureName + " title=" + title)
203 feature = Feature(featureName, parent, title, desc, index, 1, "TARGETDIR", 8)
204 self.featureTable.append(feature)
205 featureComponents = {}
207 for childElement in featureElement.childNodes:
208 if (childElement.nodeName == "feature"):
209 self.parseFeature(childElement, featureName, indexChild)
210 indexChild = indexChild + 2
211 elif (childElement.nodeName == "component"):
212 componentName = os.path.normpath(os.path.join(cwd, childElement.getAttribute("name")))
213 if(featureComponents.has_key(componentName)):
214 raise Exception("feature \"" + title + "\" contains more than one reference to \"" + componentName + "\"")
215 featureComponents[componentName] = ""
216 componentSource = os.path.normpath(childElement.getAttribute("root"))
217 print("found component reference " + componentName)
218 componentPair = self.componentForName(componentName, componentSource)
219 component = componentPair[0]
220 for msiComponent in component:
221 print("adding msi featurecomponent " + featureName + " name=" + msiComponent.name)
222 self.featurecomponentsTable.append(FeatureComponent(featureName, msiComponent.name))
224 if(not self.componentTable.has_key(msiComponent.name)):
226 for fileTuple in msiComponent.files:
227 fileKey = "f" + str(self.fileCount)
228 self.fileCount = self.fileCount + 1
231 print("component " + msiComponent.name + " keypath=" + keyPath)
232 print("adding msi file " + fileKey + " name=" + fileTuple[0] + " size=" + str(fileTuple[1]))
233 self.fileTable.append(File(fileKey, msiComponent.name, fileKey + "|" + fileTuple[0], fileTuple[1], self.fileCount))
234 self.cabList.append("\"" + fileTuple[2] + "\" " + fileKey + "\n")
235 self.componentTable[msiComponent.name] = Component(msiComponent.name, keyPath, msiComponent.directory, 0)
237 shortcut = componentPair[1]
239 shortcutName = "sc" + str(self.shortcutCount)
240 self.shortcutCount = self.shortcutCount + 1
241 self.shortcutTable.append(Shortcut(shortcutName + "|" + shortcut, "ProductShortcutFolder", component[0].name, featureName, componentPair[2]))
242 print("adding msi shortcut " + shortcut)
244 def parsePackage(self, packageElement):
246 self.code = packageElement.getAttribute("code")
248 raise Exception("invalid package code")
249 self.version = packageElement.getAttribute("version")
250 if(self.version == ""):
251 raise Exception("invalid package version")
252 self.name = packageElement.getAttribute("name")
254 raise Exception("invalid package name")
255 self.target = packageElement.getAttribute("target")
256 if(self.target == ""):
257 raise Exception("invalid target directory")
258 self.license = packageElement.getAttribute("license")
259 if(self.license == ""):
260 raise Exception("invalid package license agreement")
261 for childElement in packageElement.childNodes:
262 if (childElement.nodeName == "feature"):
263 self.parseFeature(childElement, "", index)
266 def parsePackageXML(self, filename):
267 document = parse(filename)
268 print("parsing package file " + filename)
269 self.parsePackage(document.documentElement)
271 def createPackage(self, packageFile):
272 self.directoryTable.append(Directory("TARGETDIR", "", "SourceDir"))
273 self.directoryTable.append(Directory("ProgramMenuFolder", "TARGETDIR", "."))
274 self.directoryTable.append(Directory("SystemFolder", "TARGETDIR", "."))
275 self.parsePackageXML(packageFile)
276 if(self.shortcutCount != 0):
277 self.directoryTable.append(Directory("ProductShortcutFolder", "ProgramMenuFolder", "s0|" + self.name))
279 def writeFileTable(self, name):
280 tableFile = file(name, "wt")
281 tableFile.write("File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\ns72\ts72\tl255\ti4\tS72\tS20\tI2\ti2\nFile\tFile\n")
282 for row in self.fileTable:
283 tableFile.write(row.file + "\t" + row.component + "\t" + row.filename + "\t" + str(row.filesize) + "\t" + "" + "\t" + "" + "\t" + "0" + "\t" + str(row.sequence) + "\n")
285 def writeComponentTable(self, name):
286 tableFile = file(name, "wt")
287 tableFile.write("Component\tComponentId\tDirectory_\tAttributes\tCondition\tKeyPath\ns72\tS38\ts72\ti2\tS255\tS72\nComponent\tComponent\n")
288 for k, row in self.componentTable.iteritems():
289 tableFile.write(row.name + "\t" + generate_guid() + "\t" + row.directory + "\t" + str(row.attributes) + "\t" + "" + "\t" + row.keypath + "\n")
291 def writeFeatureComponentsTable(self, name):
292 tableFile = file(name, "wt")
293 tableFile.write("Feature_\tComponent_\ns38\ts72\nFeatureComponents\tFeature_\tComponent_\n")
294 for row in self.featurecomponentsTable:
295 tableFile.write(row.feature + "\t" + row.component + "\n")
297 def writeDirectoryTable(self, name):
298 tableFile = file(name, "wt")
299 tableFile.write("Directory\tDirectory_Parent\tDefaultDir\ns72\tS72\tl255\nDirectory\tDirectory\n")
300 for row in self.directoryTable:
301 tableFile.write(row.directory + "\t" + row.parent + "\t" + row.default + "\n")
303 def writeFeatureTable(self, name):
304 tableFile = file(name, "wt")
305 tableFile.write("Feature\tFeature_Parent\tTitle\tDescription\tDisplay\tLevel\tDirectory_\tAttributes\ns38\tS38\tL64\tL255\tI2\ti2\tS72\ti2\nFeature\tFeature\n")
306 for row in self.featureTable:
307 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")
309 def writeMediaTable(self, name):
310 tableFile = file(name, "wt")
311 tableFile.write("DiskId\tLastSequence\tDiskPrompt\tCabinet\tVolumeLabel\tSource\ni2\ti2\tL64\tS255\tS32\tS72\nMedia\tDiskId\n")
312 tableFile.write("1" + "\t" + str(self.fileCount) + "\t" + "" + "\t" + "#archive.cab" + "\t" + "" + "\t" + "" + "\n")
314 def writeShortcutTable(self, name):
315 tableFile = file(name, "wt")
316 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")
317 for row in self.shortcutTable:
318 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")
320 def writeRemoveFileTable(self, name):
321 tableFile = file(name, "wt")
322 tableFile.write("FileKey\tComponent_\tFileName\tDirProperty\tInstallMode\ns72\ts72\tL255\ts72\ti2\nRemoveFile\tFileKey\n")
324 for row in self.shortcutTable:
325 tableFile.write("rf" + str(count) + "\t" + row.component + "\t" + "" + "\t" + row.directory + "\t" + "2" + "\n")
328 def writeCustomActionTable(self, name):
329 tableFile = file(name, "wt")
330 tableFile.write("Action\tType\tSource\tTarget\ns72\ti2\tS72\tS255\nCustomAction\tAction\n")
331 tableFile.write("caSetTargetDir\t51\tTARGETDIR\t" + self.target)
333 def writeUpgradeTable(self, name):
334 tableFile = file(name, "wt")
335 tableFile.write("UpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\tRemove\tActionProperty\ns38\tS20\tS20\tS255\ti4\tS255\ts72\nUpgrade\tUpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\n")
336 tableFile.write(format_guid(self.code) + "\t\t" + self.version + "\t1033\t1\t\tRELATEDPRODUCTS")
338 def writeMSILicense(self, msiName, licenseName):
339 if(not os.path.exists(licenseName)):
340 raise Exception("file not found: " + licenseName)
341 print("license=\"" + licenseName + "\"")
342 licenseFile = file(licenseName, "rt")
343 text = licenseFile.read(1024)
347 text = licenseFile.read(1024)
348 msiDB = msi.Database(msiName)
349 msiDB.setlicense(rtfString[:-1])
352 def writeMSIProperties(self, msiName):
353 msiDB = msi.Database(msiName)
354 print("ProductCode=" + format_guid(self.code))
355 msiDB.setproperty("ProductCode", format_guid(self.code))
356 print("UpgradeCode=" + format_guid(self.code))
357 msiDB.setproperty("UpgradeCode", format_guid(self.code))
358 print("ProductName=" + self.name)
359 msiDB.setproperty("ProductName", self.name)
360 print("ProductVersion=" + self.version)
361 msiDB.setproperty("ProductVersion", self.version)
362 msiDB.setproperty("RELATEDPRODUCTS", "")
363 msiDB.setproperty("SecureCustomProperties", "RELATEDPRODUCTS")
366 def writeMSI(self, msiTemplate, msiName):
367 msiWorkName = "working.msi"
368 if(os.system("copy " + msiTemplate + " " + msiWorkName) != 0):
369 raise Exception("copy failed")
370 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 + "\"")
372 self.writeMSIProperties(msiWorkName)
373 self.writeMSILicense(msiWorkName, self.license)
375 self.writeFileTable("File.idt")
376 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" File.idt")
377 os.system("del File.idt")
378 self.writeComponentTable("Component.idt")
379 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Component.idt")
380 os.system("del Component.idt")
381 self.writeFeatureComponentsTable("FeatureComponents.idt")
382 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" FeatureComponents.idt")
383 os.system("del FeatureComponents.idt")
384 self.writeDirectoryTable("Directory.idt")
385 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Directory.idt")
386 os.system("del Directory.idt")
387 self.writeFeatureTable("Feature.idt")
388 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Feature.idt")
389 os.system("del Feature.idt")
390 self.writeMediaTable("Media.idt")
391 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Media.idt")
392 os.system("del Media.idt")
393 self.writeShortcutTable("Shortcut.idt")
394 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Shortcut.idt")
395 os.system("del Shortcut.idt")
396 self.writeRemoveFileTable("RemoveFile.idt")
397 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" RemoveFile.idt")
398 os.system("del RemoveFile.idt")
399 self.writeCustomActionTable("CustomAction.idt")
400 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" CustomAction.idt")
401 os.system("del CustomAction.idt")
402 self.writeUpgradeTable("Upgrade.idt")
403 os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Upgrade.idt")
404 os.system("del Upgrade.idt")
406 cabText = file("archive_files.txt", "wt")
407 for cabDirective in self.cabList:
408 cabText.write(cabDirective)
410 if(os.system("cabarc -m LZX:21 n archive.cab @archive_files.txt") != 0):
411 raise Exception("cabarc returned error")
412 os.system("del archive_files.txt")
413 os.system("msidb -d " + msiWorkName + " -a archive.cab")
414 os.system("del archive.cab")
416 print("running standard MSI validators ...")
417 if(os.system("msival2 " + msiWorkName + " darice.cub > darice.txt") != 0):
418 raise Exception("MSI VALIDATION ERROR: see darice.txt")
419 print("running Logo Program validators ...")
420 if(os.system("msival2 " + msiWorkName + " logo.cub > logo.txt") != 0):
421 raise Exception("MSI VALIDATION ERROR: see logo.txt")
422 print("running XP Logo Program validators ...")
423 if(os.system("msival2 " + msiWorkName + " XPlogo.cub > XPlogo.txt") != 0):
424 raise Exception("MSI VALIDATION ERROR: see XPlogo.txt")
426 msiNameQuoted = "\"" + msiName + "\""
427 if(os.path.exists(os.path.join(".\\", msiName)) and os.system("del " + msiNameQuoted) != 0):
428 raise Exception("failed to delete old target")
429 if(os.system("rename " + msiWorkName + " " + msiNameQuoted) != 0):
430 raise Exception("failed to rename new target")