installer.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. # Copyright (C) 2001-2006 William Joseph.
  2. #
  3. # This file is part of GtkRadiant.
  4. #
  5. # GtkRadiant is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # GtkRadiant is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with GtkRadiant; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. import os.path
  19. import xml.dom
  20. import os
  21. import stat
  22. import string
  23. from xml.dom.minidom import parse
  24. import msi
  25. cwd = os.getcwd()
  26. print("cwd=" + cwd)
  27. def format_guid(guid):
  28. return "{" + guid.upper() + "}"
  29. def generate_guid():
  30. os.system("uuidgen > tmp_uuid.txt")
  31. uuidFile = file("tmp_uuid.txt", "rt")
  32. guid = format_guid(uuidFile.read(36))
  33. uuidFile.close()
  34. os.system("del tmp_uuid.txt")
  35. return guid
  36. def path_components(path):
  37. directories = []
  38. remaining = path
  39. while(remaining != ""):
  40. splitPath = os.path.split(remaining)
  41. remaining = splitPath[0]
  42. directories.append(splitPath[1])
  43. directories.reverse()
  44. return directories
  45. class Feature:
  46. def __init__(self, feature, parent, title, desc, display, level, directory, attributes):
  47. self.feature = feature
  48. self.parent = parent
  49. self.title = title
  50. self.desc = desc
  51. self.display = display
  52. self.level = level
  53. self.directory = directory
  54. self.attributes = attributes
  55. class FeatureComponent:
  56. def __init__(self, feature, component):
  57. self.feature = feature
  58. self.component = component
  59. class Directory:
  60. def __init__(self, directory, parent, default):
  61. self.directory = directory
  62. self.parent = parent
  63. self.default = default
  64. class Component:
  65. def __init__(self, name, keypath, directory, attributes):
  66. self.name = name
  67. self.keypath = keypath
  68. self.directory = directory
  69. self.attributes = attributes
  70. class File:
  71. def __init__(self, file, component, filename, filesize, sequence):
  72. self.file = file
  73. self.component = component
  74. self.filename = filename
  75. self.filesize = filesize
  76. self.sequence = sequence
  77. class Shortcut:
  78. def __init__(self, name, directory, component, feature, icon):
  79. self.name = name
  80. self.directory = directory
  81. self.component = component
  82. self.feature = feature
  83. self.icon = icon
  84. class ComponentFiles:
  85. def __init__(self, name, files, directory):
  86. self.name = name
  87. self.files = files
  88. self.directory = directory
  89. class MSIPackage:
  90. def __init__(self, packageFile):
  91. self.code = ""
  92. self.name = ""
  93. self.version = ""
  94. self.target = ""
  95. self.license = ""
  96. self.cabList = []
  97. self.featureCount = 0
  98. self.featureTable = []
  99. self.featurecomponentsTable = []
  100. self.componentCache = {}
  101. self.componentCount = 0
  102. self.componentTable = {}
  103. self.directoryTree = {}
  104. self.directoryCount = 0
  105. self.directoryTable = []
  106. self.fileCount = 0
  107. self.fileTable = []
  108. self.shortcutCount = 0
  109. self.shortcutTable = []
  110. self.createPackage(packageFile)
  111. def addDirectory(self, directoryName, parentKey, directory):
  112. if(not directory.has_key(directoryName)):
  113. directoryKey = "d" + str(self.directoryCount)
  114. self.directoryCount = self.directoryCount + 1
  115. print("adding msi directory " + directoryKey + " parent=" + parentKey + " name=" + directoryName)
  116. self.directoryTable.append(Directory(directoryKey, parentKey, directoryKey + "|" + directoryName))
  117. directory[directoryName] = (directoryKey, {})
  118. else:
  119. print("ignored duplicate directory " + directoryName)
  120. return directory[directoryName]
  121. def parseComponentTree(self, treeElement, parent, directory, directoryPath, component):
  122. files = []
  123. for childElement in treeElement.childNodes:
  124. if (childElement.nodeName == "file"):
  125. fileName = childElement.getAttribute("name")
  126. filePath = os.path.join(directoryPath, fileName)
  127. if(fileName != "" and os.path.exists(filePath)):
  128. print("found file " + filePath)
  129. file = (fileName, os.path.getsize(filePath), filePath)
  130. files.append(file)
  131. else:
  132. raise Exception("file not found " + filePath)
  133. if (childElement.nodeName == "dir"):
  134. directoryName = childElement.getAttribute("name")
  135. print("found directory " + directoryName)
  136. directoryPair = self.addDirectory(directoryName, parent, directory)
  137. self.parseComponentTree(childElement, directoryPair[0], directoryPair[1], os.path.join(directoryPath, directoryName), component)
  138. count = len(files)
  139. if(count != 0):
  140. componentKey = "c" + str(self.componentCount)
  141. self.componentCount = self.componentCount + 1
  142. msiComponent = ComponentFiles(componentKey, files, parent);
  143. print("adding msi component " + msiComponent.name + " with " + str(count) + " file(s)")
  144. component.append(msiComponent)
  145. def parseComponent(self, componentElement, rootPath):
  146. shortcut = componentElement.getAttribute("shortcut")
  147. icon = componentElement.getAttribute("icon")
  148. component = []
  149. subDirectory = componentElement.getAttribute("subdirectory")
  150. directoryPair = ("TARGETDIR", self.directoryTree)
  151. for directoryName in path_components(subDirectory):
  152. directoryPair = self.addDirectory(directoryName, directoryPair[0], directoryPair[1])
  153. self.parseComponentTree(componentElement, directoryPair[0], directoryPair[1], rootPath, component)
  154. component.reverse()
  155. print("component requires " + str(len(component)) + " msi component(s)")
  156. return (component, shortcut, icon)
  157. def parseComponentXML(self, filename, rootPath):
  158. componentDocument = parse(filename)
  159. print("parsing component file " + filename)
  160. componentElement = componentDocument.documentElement
  161. return self.parseComponent(componentElement, rootPath)
  162. def componentForName(self, name, rootPath):
  163. if(self.componentCache.has_key(name)):
  164. return self.componentCache[name]
  165. else:
  166. component = self.parseComponentXML(name, rootPath)
  167. self.componentCache[name] = component
  168. return component
  169. def parseFeature(self, featureElement, parent, index):
  170. featureName = "ft" + str(self.featureCount)
  171. self.featureCount = self.featureCount + 1
  172. title = featureElement.getAttribute("name")
  173. desc = featureElement.getAttribute("desc")
  174. print("adding msi feature " + featureName + " title=" + title)
  175. feature = Feature(featureName, parent, title, desc, index, 1, "TARGETDIR", 8)
  176. self.featureTable.append(feature)
  177. featureComponents = {}
  178. indexChild = 2
  179. for childElement in featureElement.childNodes:
  180. if (childElement.nodeName == "feature"):
  181. self.parseFeature(childElement, featureName, indexChild)
  182. indexChild = indexChild + 2
  183. elif (childElement.nodeName == "component"):
  184. componentName = os.path.normpath(os.path.join(cwd, childElement.getAttribute("name")))
  185. if(featureComponents.has_key(componentName)):
  186. raise Exception("feature \"" + title + "\" contains more than one reference to \"" + componentName + "\"")
  187. featureComponents[componentName] = ""
  188. componentSource = os.path.normpath(childElement.getAttribute("root"))
  189. print("found component reference " + componentName)
  190. componentPair = self.componentForName(componentName, componentSource)
  191. component = componentPair[0]
  192. for msiComponent in component:
  193. print("adding msi featurecomponent " + featureName + " name=" + msiComponent.name)
  194. self.featurecomponentsTable.append(FeatureComponent(featureName, msiComponent.name))
  195. if(not self.componentTable.has_key(msiComponent.name)):
  196. keyPath = ""
  197. for fileTuple in msiComponent.files:
  198. fileKey = "f" + str(self.fileCount)
  199. self.fileCount = self.fileCount + 1
  200. if(keyPath == ""):
  201. keyPath = fileKey
  202. print("component " + msiComponent.name + " keypath=" + keyPath)
  203. print("adding msi file " + fileKey + " name=" + fileTuple[0] + " size=" + str(fileTuple[1]))
  204. self.fileTable.append(File(fileKey, msiComponent.name, fileKey + "|" + fileTuple[0], fileTuple[1], self.fileCount))
  205. self.cabList.append("\"" + fileTuple[2] + "\" " + fileKey + "\n")
  206. self.componentTable[msiComponent.name] = Component(msiComponent.name, keyPath, msiComponent.directory, 0)
  207. shortcut = componentPair[1]
  208. if(shortcut != ""):
  209. shortcutName = "sc" + str(self.shortcutCount)
  210. self.shortcutCount = self.shortcutCount + 1
  211. self.shortcutTable.append(Shortcut(shortcutName + "|" + shortcut, "ProductShortcutFolder", component[0].name, featureName, componentPair[2]))
  212. print("adding msi shortcut " + shortcut)
  213. def parsePackage(self, packageElement):
  214. index = 2
  215. self.code = packageElement.getAttribute("code")
  216. if(self.code == ""):
  217. raise Exception("invalid package code")
  218. self.version = packageElement.getAttribute("version")
  219. if(self.version == ""):
  220. raise Exception("invalid package version")
  221. self.name = packageElement.getAttribute("name")
  222. if(self.name == ""):
  223. raise Exception("invalid package name")
  224. self.target = packageElement.getAttribute("target")
  225. if(self.target == ""):
  226. raise Exception("invalid target directory")
  227. self.license = packageElement.getAttribute("license")
  228. if(self.license == ""):
  229. raise Exception("invalid package license agreement")
  230. for childElement in packageElement.childNodes:
  231. if (childElement.nodeName == "feature"):
  232. self.parseFeature(childElement, "", index)
  233. index = index + 2
  234. def parsePackageXML(self, filename):
  235. document = parse(filename)
  236. print("parsing package file " + filename)
  237. self.parsePackage(document.documentElement)
  238. def createPackage(self, packageFile):
  239. self.directoryTable.append(Directory("TARGETDIR", "", "SourceDir"))
  240. self.directoryTable.append(Directory("ProgramMenuFolder", "TARGETDIR", "."))
  241. self.directoryTable.append(Directory("SystemFolder", "TARGETDIR", "."))
  242. self.parsePackageXML(packageFile)
  243. if(self.shortcutCount != 0):
  244. self.directoryTable.append(Directory("ProductShortcutFolder", "ProgramMenuFolder", "s0|" + self.name))
  245. def writeFileTable(self, name):
  246. tableFile = file(name, "wt")
  247. tableFile.write("File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\ns72\ts72\tl255\ti4\tS72\tS20\tI2\ti2\nFile\tFile\n")
  248. for row in self.fileTable:
  249. tableFile.write(row.file + "\t" + row.component + "\t" + row.filename + "\t" + str(row.filesize) + "\t" + "" + "\t" + "" + "\t" + "0" + "\t" + str(row.sequence) + "\n")
  250. def writeComponentTable(self, name):
  251. tableFile = file(name, "wt")
  252. tableFile.write("Component\tComponentId\tDirectory_\tAttributes\tCondition\tKeyPath\ns72\tS38\ts72\ti2\tS255\tS72\nComponent\tComponent\n")
  253. for k, row in self.componentTable.iteritems():
  254. tableFile.write(row.name + "\t" + generate_guid() + "\t" + row.directory + "\t" + str(row.attributes) + "\t" + "" + "\t" + row.keypath + "\n")
  255. def writeFeatureComponentsTable(self, name):
  256. tableFile = file(name, "wt")
  257. tableFile.write("Feature_\tComponent_\ns38\ts72\nFeatureComponents\tFeature_\tComponent_\n")
  258. for row in self.featurecomponentsTable:
  259. tableFile.write(row.feature + "\t" + row.component + "\n")
  260. def writeDirectoryTable(self, name):
  261. tableFile = file(name, "wt")
  262. tableFile.write("Directory\tDirectory_Parent\tDefaultDir\ns72\tS72\tl255\nDirectory\tDirectory\n")
  263. for row in self.directoryTable:
  264. tableFile.write(row.directory + "\t" + row.parent + "\t" + row.default + "\n")
  265. def writeFeatureTable(self, name):
  266. tableFile = file(name, "wt")
  267. tableFile.write("Feature\tFeature_Parent\tTitle\tDescription\tDisplay\tLevel\tDirectory_\tAttributes\ns38\tS38\tL64\tL255\tI2\ti2\tS72\ti2\nFeature\tFeature\n")
  268. for row in self.featureTable:
  269. 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")
  270. def writeMediaTable(self, name):
  271. tableFile = file(name, "wt")
  272. tableFile.write("DiskId\tLastSequence\tDiskPrompt\tCabinet\tVolumeLabel\tSource\ni2\ti2\tL64\tS255\tS32\tS72\nMedia\tDiskId\n")
  273. tableFile.write("1" + "\t" + str(self.fileCount) + "\t" + "" + "\t" + "#archive.cab" + "\t" + "" + "\t" + "" + "\n")
  274. def writeShortcutTable(self, name):
  275. tableFile = file(name, "wt")
  276. 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")
  277. for row in self.shortcutTable:
  278. 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")
  279. def writeRemoveFileTable(self, name):
  280. tableFile = file(name, "wt")
  281. tableFile.write("FileKey\tComponent_\tFileName\tDirProperty\tInstallMode\ns72\ts72\tL255\ts72\ti2\nRemoveFile\tFileKey\n")
  282. count = 0
  283. for row in self.shortcutTable:
  284. tableFile.write("rf" + str(count) + "\t" + row.component + "\t" + "" + "\t" + row.directory + "\t" + "2" + "\n")
  285. count = count + 1
  286. def writeCustomActionTable(self, name):
  287. tableFile = file(name, "wt")
  288. tableFile.write("Action\tType\tSource\tTarget\ns72\ti2\tS72\tS255\nCustomAction\tAction\n")
  289. tableFile.write("caSetTargetDir\t51\tTARGETDIR\t" + self.target)
  290. def writeUpgradeTable(self, name):
  291. tableFile = file(name, "wt")
  292. tableFile.write("UpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\tRemove\tActionProperty\ns38\tS20\tS20\tS255\ti4\tS255\ts72\nUpgrade\tUpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\n")
  293. tableFile.write(format_guid(self.code) + "\t\t" + self.version + "\t1033\t1\t\tRELATEDPRODUCTS")
  294. def writeMSILicense(self, msiName, licenseName):
  295. if(not os.path.exists(licenseName)):
  296. raise Exception("file not found: " + licenseName)
  297. print("license=\"" + licenseName + "\"")
  298. licenseFile = file(licenseName, "rt")
  299. text = licenseFile.read(1024)
  300. rtfString = ""
  301. while(text != ""):
  302. rtfString += text
  303. text = licenseFile.read(1024)
  304. msiDB = msi.Database(msiName)
  305. msiDB.setlicense(rtfString[:-1])
  306. msiDB.commit()
  307. def writeMSIProperties(self, msiName):
  308. msiDB = msi.Database(msiName)
  309. print("ProductCode=" + format_guid(self.code))
  310. msiDB.setproperty("ProductCode", format_guid(self.code))
  311. print("UpgradeCode=" + format_guid(self.code))
  312. msiDB.setproperty("UpgradeCode", format_guid(self.code))
  313. print("ProductName=" + self.name)
  314. msiDB.setproperty("ProductName", self.name)
  315. print("ProductVersion=" + self.version)
  316. msiDB.setproperty("ProductVersion", self.version)
  317. msiDB.setproperty("RELATEDPRODUCTS", "")
  318. msiDB.setproperty("SecureCustomProperties", "RELATEDPRODUCTS")
  319. msiDB.commit()
  320. def writeMSI(self, msiTemplate, msiName):
  321. msiWorkName = "working.msi"
  322. if(os.system("copy " + msiTemplate + " " + msiWorkName) != 0):
  323. raise Exception("copy failed")
  324. 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 + "\"")
  325. self.writeMSIProperties(msiWorkName)
  326. self.writeMSILicense(msiWorkName, self.license)
  327. self.writeFileTable("File.idt")
  328. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" File.idt")
  329. os.system("del File.idt")
  330. self.writeComponentTable("Component.idt")
  331. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Component.idt")
  332. os.system("del Component.idt")
  333. self.writeFeatureComponentsTable("FeatureComponents.idt")
  334. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" FeatureComponents.idt")
  335. os.system("del FeatureComponents.idt")
  336. self.writeDirectoryTable("Directory.idt")
  337. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Directory.idt")
  338. os.system("del Directory.idt")
  339. self.writeFeatureTable("Feature.idt")
  340. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Feature.idt")
  341. os.system("del Feature.idt")
  342. self.writeMediaTable("Media.idt")
  343. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Media.idt")
  344. os.system("del Media.idt")
  345. self.writeShortcutTable("Shortcut.idt")
  346. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Shortcut.idt")
  347. os.system("del Shortcut.idt")
  348. self.writeRemoveFileTable("RemoveFile.idt")
  349. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" RemoveFile.idt")
  350. os.system("del RemoveFile.idt")
  351. self.writeCustomActionTable("CustomAction.idt")
  352. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" CustomAction.idt")
  353. os.system("del CustomAction.idt")
  354. self.writeUpgradeTable("Upgrade.idt")
  355. os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Upgrade.idt")
  356. os.system("del Upgrade.idt")
  357. cabText = file("archive_files.txt", "wt")
  358. for cabDirective in self.cabList:
  359. cabText.write(cabDirective)
  360. cabText.close()
  361. if(os.system("cabarc -m LZX:21 n archive.cab @archive_files.txt") != 0):
  362. raise Exception("cabarc returned error")
  363. os.system("del archive_files.txt")
  364. os.system("msidb -d " + msiWorkName + " -a archive.cab")
  365. os.system("del archive.cab")
  366. print("running standard MSI validators ...")
  367. if(os.system("msival2 " + msiWorkName + " darice.cub > darice.txt") != 0):
  368. raise Exception("MSI VALIDATION ERROR: see darice.txt")
  369. print("running Logo Program validators ...")
  370. if(os.system("msival2 " + msiWorkName + " logo.cub > logo.txt") != 0):
  371. raise Exception("MSI VALIDATION ERROR: see logo.txt")
  372. print("running XP Logo Program validators ...")
  373. if(os.system("msival2 " + msiWorkName + " XPlogo.cub > XPlogo.txt") != 0):
  374. raise Exception("MSI VALIDATION ERROR: see XPlogo.txt")
  375. msiNameQuoted = "\"" + msiName + "\""
  376. if(os.path.exists(os.path.join(".\\", msiName)) and os.system("del " + msiNameQuoted) != 0):
  377. raise Exception("failed to delete old target")
  378. if(os.system("rename " + msiWorkName + " " + msiNameQuoted) != 0):
  379. raise Exception("failed to rename new target")