00001 import msilib, schema, sequence, os, sets, glob 00002 from msilib import Feature, CAB, Directory, Dialog, Binary, add_data 00003 import uisample 00004 from win32com.client import constants 00005 import PyDialog 00006 00007 class msiMaker(): 00008 def __init__(self,_package_name,_package_author,_srcdir,_current_version): 00009 msilib.Win64 = 0 00010 self.current_version = _current_version 00011 self.testpackage=0 00012 self.package_name = _package_name 00013 self.package_author = _package_author 00014 self.srcdir = _srcdir 00015 self.major, self.minor = self.current_version.split(".")[:2] 00016 self.short_version = self.major+"."+self.minor 00017 self.upgrade_code='{92A24481-3ECB-40FC-8836-04B7966EC0D5}' # This should never change 00018 self.product_codes = { 00019 _current_version : msilib.gen_uuid() 00020 } 00021 self.schema = schema 00022 self.sequence = sequence 00023 self.isDebug = True 00024 msilib.reset() 00025 00026 def build_database(self): 00027 self.db = msilib.init_database("%s%s.msi" % (self.package_name,self.current_version),self.schema, 00028 "%s %s" % (self.package_name,self.current_version), 00029 self.product_codes[self.current_version], 00030 self.current_version, 00031 self.package_author) 00032 if (self.isDebug): 00033 print '(build_database) :: db=(%s)' % (str(self.db)) 00034 msilib.add_tables(self.db, self.sequence) 00035 self.db.Commit() 00036 00037 def add_ui(self): 00038 x = y = 50 00039 w = 370 00040 h = 300 00041 title = "[ProductName] Setup" 00042 00043 # Dialog styles 00044 modal = 3 # visible | modal 00045 modeless = 1 # visible 00046 track_disk_space = 32 00047 00048 add_data(self.db, 'ActionText', uisample.ActionText) 00049 add_data(self.db, 'UIText', uisample.UIText) 00050 00051 # Bitmaps 00052 #add_data(self.db, "Binary", 00053 # [("PythonWin", msilib.Binary(srcdir+r"\PCbuild\installer.bmp")), # 152x328 pixels 00054 # ("Up",msilib.Binary("Up.bin")), 00055 # ("New",msilib.Binary("New.bin")), 00056 # ("InfoIcon",msilib.Binary("info.bin")), 00057 # ("ExclamationIcon",msilib.Binary("exclamic.bin")), 00058 # ]) 00059 00060 # UI customization properties 00061 add_data(self.db, "Property", 00062 [("DefaultUIFont", "DlgFont8"), 00063 ("ErrorDialog", "ErrorDlg"), 00064 ("Progress1", "Install"), # modified in maintenance type dlg 00065 ("Progress2", "installs"), 00066 ("MaintenanceForm_Action", "Repair")]) 00067 00068 # Fonts 00069 add_data(self.db, "TextStyle", 00070 [("DlgFont8", "Tahoma", 9, None, 0), 00071 ("DlgFontBold8", "Tahoma", 8, None, 1), #bold 00072 ("VerdanaBold13", "Verdana", 13, None, 1), 00073 ]) 00074 00075 # Custom actions 00076 add_data(self.db, "CustomAction", [ 00077 # msidbCustomActionTypeFirstSequence + msidbCustomActionTypeTextData + msidbCustomActionTypeProperty 00078 ("InitialTargetDir", 307, "TARGETDIR", 00079 "[WindowsVolume]Python%s%s" % (self.major, self.minor)) 00080 ]) 00081 00082 # UI Sequences 00083 add_data(self.db, "InstallUISequence", 00084 [("PrepareDlg", None, 140), 00085 ("InitialTargetDir", 'TARGETDIR=""', 750), 00086 ("SelectDirectoryDlg", "Not Installed", 1230), 00087 # XXX notyet 00088 #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), 00089 ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), 00090 ("ProgressDlg", None, 1280)]) 00091 add_data(self.db, "AdminUISequence", 00092 [("InitialTargetDir", 'TARGETDIR=""', 750)]) 00093 00094 # Standard dialogs: FatalError, UserExit, ExitDialog 00095 fatal=PyDialog.PyDialog(self.db, "FatalError", x, y, w, h, modal, title, "Finish", "Finish", "Finish") 00096 fatal.title("[ProductName] Installer ended prematurely") 00097 fatal.back("< Back", "Finish", active = 0) 00098 fatal.cancel("Cancel", "Back", active = 0) 00099 fatal.text("Description1", 135, 70, 220, 60, 196611, 00100 "[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.") 00101 fatal.text("Description2", 135, 135, 220, 20, 196611, 00102 "Click the Finish button to exit the Installer.") 00103 c=fatal.next("Finish", "Cancel", name="Finish") 00104 c.event("EndDialog", "Exit") 00105 00106 user_exit=PyDialog.PyDialog(self.db, "UserExit", x, y, w, h, modal, title, "Finish", "Finish", "Finish") 00107 user_exit.title("[ProductName] Installer was interrupted") 00108 user_exit.back("< Back", "Finish", active = 0) 00109 user_exit.cancel("Cancel", "Back", active = 0) 00110 user_exit.text("Description1", 135, 70, 220, 40, 196611, 00111 "[ProductName] setup was interrupted. Your system has not been modified. " 00112 "To install this program at a later time, please run the installation again.") 00113 user_exit.text("Description2", 135, 115, 220, 20, 196611, "Click the Finish button to exit the Installer.") 00114 c = user_exit.next("Finish", "Cancel", name="Finish") 00115 c.event("EndDialog", "Exit") 00116 00117 exit_dialog = PyDialog.PyDialog(self.db, "ExitDialog", x, y, w, h, modal, title, "Finish", "Finish", "Finish") 00118 exit_dialog.title("Completing the [ProductName] Installer") 00119 exit_dialog.back("< Back", "Finish", active = 0) 00120 exit_dialog.cancel("Cancel", "Back", active = 0) 00121 exit_dialog.text("Description", 135, 115, 220, 20, 196611, "Click the Finish button to exit the Installer.") 00122 c = exit_dialog.next("Finish", "Cancel", name="Finish") 00123 c.event("EndDialog", "Return") 00124 00125 # Required dialog: FilesInUse, ErrorDlg 00126 inuse = PyDialog.PyDialog(self.db, "FilesInUse", x, y, w, h, 19, title, "Retry", "Retry", "Retry", bitmap=False) 00127 inuse.text("Title", 15, 6, 200, 15, 196611, r"{\DlgFontBold8}Files in Use") 00128 inuse.text("Description", 20, 23, 280, 20, 196611, "Some files that need to be updated are currently in use.") 00129 inuse.text("Text", 20, 55, 330, 50, 3, 00130 "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.") 00131 inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", None, None, None) 00132 c=inuse.back("Exit", "Ignore", name="Exit") 00133 c.event("EndDialog", "Exit") 00134 c=inuse.next("Ignore", "Retry", name="Ignore") 00135 c.event("EndDialog", "Ignore") 00136 c=inuse.cancel("Retry", "Exit", name="Retry") 00137 c.event("EndDialog","Retry") 00138 00139 error = Dialog(self.db, "ErrorDlg", 50, 10, 330, 101, 65543, title, "ErrorText", None, None) 00140 error.text("ErrorText", 50,9,280,48,3, "") 00141 error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "InfoIcon", None, None) 00142 error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") 00143 error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") 00144 error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") 00145 error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") 00146 error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") 00147 error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") 00148 error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") 00149 00150 # Global "Query Cancel" dialog 00151 cancel = Dialog(self.db, "CancelDlg", 50, 10, 260, 85, 3, title, "No", "No", "No") 00152 cancel.text("Text", 48, 15, 194, 30, 3, "Are you sure you want to cancel [ProductName] installation?") 00153 cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, "InfoIcon", None, None) 00154 c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") 00155 c.event("EndDialog", "Exit") 00156 c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") 00157 c.event("EndDialog", "Return") 00158 00159 # Global "Wait for costing" dialog 00160 costing = Dialog(self.db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, "Return", "Return", "Return") 00161 costing.text("Text", 48, 15, 194, 30, 3, 00162 "Please wait while the installer finishes determining your disk space requirements.") 00163 costing.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, "ExclamationIcon", None, None) 00164 c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) 00165 c.event("EndDialog", "Exit") 00166 00167 # Preparation dialog: no user input except cancellation 00168 prep = PyDialog.PyDialog(self.db, "PrepareDlg", x, y, w, h, modeless, title, "Cancel", "Cancel", "Cancel") 00169 prep.text("Description", 135, 70, 220, 40, 196611, 00170 "Please wait while the Installer prepares to guide you through the installation.") 00171 prep.title("Welcome to the [ProductName] Installer") 00172 c=prep.text("ActionText", 135, 110, 220, 20, 196611, "Pondering...") 00173 c.mapping("AxtionText", "Text") 00174 c=prep.text("ActionData", 135, 135, 220, 30, 196611, None) 00175 c.mapping("ActionData", "Text") 00176 prep.back("Back", None, active=0) 00177 prep.next("Next", None, active=0) 00178 c=prep.cancel("Cancel", None) 00179 c.event("SpawnDialog", "CancelDlg") 00180 00181 # Target directory selection 00182 seldlg = PyDialog.PyDialog(self.db, "SelectDirectoryDlg", x, y, w, h, modal, title, "Next", "Next", "Cancel") 00183 seldlg.title("Select Destination Directory") 00184 seldlg.text("Description", 135, 50, 220, 40, 196611, "Please select a directory for the [ProductName] files.") 00185 00186 seldlg.back("< Back", None, active=0) 00187 c = seldlg.next("Next >", "Cancel") 00188 c.event("SetTargetPath", "TARGETDIR", order=1) 00189 c.event("SpawnWaitDialog", "WaitForCostingDlg", "CostingComplete = 1", 2) 00190 c.event("NewDialog", "SelectFeaturesDlg", order=3) 00191 00192 c = seldlg.cancel("Cancel", "DirectoryCombo") 00193 c.event("SpawnDialog", "CancelDlg") 00194 00195 seldlg.control("DirectoryCombo", "DirectoryCombo", 135, 70, 172, 80, 393219, "TARGETDIR", None, "DirectoryList", None) 00196 seldlg.control("DirectoryList", "DirectoryList", 135, 90, 208, 136, 3, "TARGETDIR", None, "PathEdit", None) 00197 seldlg.control("PathEdit", "PathEdit", 135, 230, 206, 16, 3, "TARGETDIR", None, "Next", None) 00198 c = seldlg.pushbutton("Up", 306, 70, 18, 18, 3670019, "Up", None) 00199 c.event("DirectoryListUp", "0") 00200 c = seldlg.pushbutton("NewDir", 324, 70, 18, 18, 3670019, "New", None) 00201 c.event("DirectoryListNew", "0") 00202 00203 # SelectFeaturesDlg 00204 features = PyDialog.PyDialog(self.db, "SelectFeaturesDlg", x, y, w, h, modal|track_disk_space, title, "Tree", "Next", "Cancel") 00205 features.title("Customize [ProductName]") 00206 features.text("Description", 135, 35, 220, 15, 196611, "Select the way you want features to be installed.") 00207 features.text("Text", 135,45,220,30, 3, 00208 "Click on the icons in the tree below to change the way features will be installed.") 00209 00210 c=features.back("< Back", "Next") 00211 c.event("NewDialog", "SelectDirectoryDlg") # XXX InstallMode="" 00212 00213 c=features.next("Next >", "Cancel") 00214 c.mapping("SelectionNoItems", "Enabled") 00215 c.event("EndDialog", "Return") 00216 00217 c=features.cancel("Cancel", "Tree") 00218 c.event("SpawnDialog", "CancelDlg") 00219 00220 # The browse property is not used, since we have only a single target path (selected already) 00221 features.control("Tree", "SelectionTree", 135, 75, 220, 95, 7, "_BrowseProperty", "Tree of selections", "Back", None) 00222 00223 #c=features.pushbutton("Reset", 42, 243, 56, 17, 3, "Reset", "DiskCost") 00224 #c.mapping("SelectionNoItems", "Enabled") 00225 #c.event("Reset", "0") 00226 00227 features.control("Box", "GroupBox", 135, 170, 225, 90, 1, None, None, None, None) 00228 00229 c=features.xbutton("DiskCost", "Disk &Usage", None, 0.10) 00230 c.mapping("SelectionNoItems","Enabled") 00231 c.event("SpawnDialog", "DiskCostDlg") 00232 00233 c=features.text("ItemDescription", 140, 180, 210, 50, 3, "Multiline description of the currently selected item.") 00234 c.mapping("SelectionDescription","Text") 00235 00236 c=features.text("ItemSize", 140, 230, 220, 25, 3, "The size of the currently selected item.") 00237 c.mapping("SelectionSize", "Text") 00238 00239 # Disk cost 00240 cost = PyDialog.PyDialog(self.db, "DiskCostDlg", x, y, w, h, modal, title, "OK", "OK", "OK", bitmap=False) 00241 cost.text("Title", 15, 6, 200, 15, 196611, "{\DlgFontBold8}Disk Space Requirements") 00242 cost.text("Description", 20, 20, 280, 20, 196611, 00243 "The disk space required for the installation of the selected features.") 00244 cost.text("Text", 20, 53, 330, 60, 3, 00245 "The highlighted volumes (if any) do not have enough disk space " 00246 "available for the currently selected features. You can either " 00247 "remove some files from the highlighted volumes, or choose to " 00248 "install less features onto local drive(s), or select different " 00249 "destination drive(s).") 00250 cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, None, "{120}{70}{70}{70}{70}", None, None) 00251 cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") 00252 00253 00254 # Installation Progress dialog (modeless) 00255 progress = PyDialog.PyDialog(self.db, "ProgressDlg", x, y, w, h, modeless, title, "Cancel", "Cancel", "Cancel", bitmap=False) 00256 progress.text("Title", 20, 15, 200, 15, 196611, "{\DlgFontBold8}[Progress1] [ProductName]") 00257 progress.text("Text", 35, 65, 300, 30, 3, 00258 "Please wait while the Installer [Progress2] [ProductName]. " 00259 "This may take several minutes.") 00260 progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") 00261 00262 c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") 00263 c.mapping("ActionText", "Text") 00264 00265 #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) 00266 #c.mapping("ActionData", "Text") 00267 00268 c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, None, "Progress done", None, None) 00269 c.mapping("SetProgress", "Progress") 00270 00271 progress.back("< Back", "Next", active=False) 00272 progress.next("Next >", "Cancel", active=False) 00273 progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") 00274 00275 # Maintenance type: repair/uninstall 00276 maint = PyDialog.PyDialog(self.db, "MaintenanceTypeDlg", x, y, w, h, modal, title, "Next", "Next", "Cancel") 00277 maint.title("Welcome to the [ProductName] Setup Wizard") 00278 maint.text("BodyText", 135, 63, 230, 42, 3, "Select whether you want to repair or remove [ProductName].") 00279 g=maint.radiogroup("RepairRadioGroup", 135, 108, 230, 48, 3, "MaintenanceForm_Action", "", "Next") 00280 g.add("Repair", 0, 0, 200, 17, "&Repair [ProductName]") 00281 g.add("Remove", 0, 18, 200, 17, "Re&move [ProductName]") 00282 00283 maint.back("< Back", None, active=False) 00284 c=maint.next("Finish", "Cancel") 00285 # Reinstall: Change progress dialog to "Repair", then invoke reinstall 00286 # Also set list of reinstalled features to "ALL" 00287 c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 1) 00288 c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 2) 00289 c.event("[Progress2]", "repaires", 'MaintenanceForm_Action="Repair"', 3) 00290 c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 4) 00291 00292 # Uninstall: Change progress to "Remove", then invoke uninstall 00293 # Also set list of removed features to "ALL" 00294 c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) 00295 c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) 00296 c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) 00297 c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) 00298 00299 # Close dialog when maintenance action scheduled 00300 c.event("EndDialog", "Return", order=20) 00301 00302 maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") 00303 00304 00305 def add_features(self): 00306 global default_feature 00307 if (self.isDebug): 00308 print '(add_features).1 :: db=(%s)' % (str(self.db)) 00309 default_feature = Feature(self.db, "DefaultFeature", "DSS", "DSS Installation", 1, directory = "TARGETDIR") 00310 #tcltk = Feature(self.db, "TclTk", "Tcl/Tk", "Tkinter, IDLE, pydoc", 3) 00311 #htmlfiles = Feature(self.db, "Documentation", "Documentation", "Python HTMLHelp File", 5) 00312 #tools = Feature(self.db, "Tools", "Utility Scripts", "Python utility scripts (Tools/", 7) 00313 #testsuite = Feature(self.db, "Testsuite", "Test suite", "Python test suite (Lib/test/)", 9) 00314 00315 def _add_files(self): 00316 self.cab = CAB(self.package_name) 00317 self.root = Directory(self.db, self.cab, None, self.srcdir, "TARGETDIR", "SourceDir") 00318 00319 # Add all other root files into the TARGETDIR component 00320 self.root.start_component("TARGETDIR", default_feature) 00321 self.dirs = {} 00322 pydirs = [(self.root,"Lib")] 00323 while pydirs: 00324 parent, dir = pydirs.pop() 00325 print '(_add_files) :: parent=(%s), dir=(%s)' % (str(parent),str(dir)) 00326 if dir == "CVS" or dir.startswith("plat-"): 00327 continue 00328 else: 00329 default_feature.set_current() 00330 lib = Directory(self.db, self.cab, parent, dir, dir, "%s|%s" % (parent.make_short(dir), dir)) 00331 self.dirs[dir]=lib 00332 for f in os.listdir(lib.absolute): 00333 if os.path.isdir(os.path.join(lib.absolute, f)): 00334 pydirs.append((lib, f)) 00335 print '(_add_files) :: self.dirs=(%s)' % (str(self.dirs)) 00336 00337 def add_files(self,files): 00338 self.cab = CAB(self.package_name) 00339 self.root = Directory(self.db, self.cab, None, '\\', "TARGETDIR", "SourceDir") 00340 print '(add_files) :: self.srcdir=(%s)' % (self.srcdir) 00341 00342 dirs={} 00343 # Add all other root files into the TARGETDIR component 00344 self.root.start_component("TARGETDIR", default_feature) 00345 # BEGIN: This block MUST remain intact, as-is or bad things may happen... 00346 _cur_dir = '\\' 00347 lib = self.root 00348 # END! This block MUST remain intact, as-is or bad things may happen... 00349 _parent = self.root 00350 _dir = "Lib" 00351 for f in files: 00352 if ( (str(f.__class__).find("'tuple'") > -1) or (str(f.__class__).find("'list'") > -1) ): 00353 _f = f[1] 00354 else: 00355 _f = f 00356 f_dir = os.path.dirname(_f) 00357 print '(add_files) :: f_dir=(%s), _f=(%s)' % (f_dir,_f) 00358 if (_cur_dir != f_dir): 00359 print '(add_files) :: NEW DIRECTORY !\n' 00360 _dir = f_dir # .split(os.sep)[-1] 00361 lib = Directory(self.db, self.cab, _parent, _dir, _dir, "%s|%s" % (_parent.make_short(_dir), _dir)) 00362 dirs[dir]=lib 00363 _cur_dir = f_dir 00364 lib.add_file(_f.split(os.sep)[-1]) 00365 00366 self.cab.commit(self.db) 00367 00368 def add_registry(self): 00369 # File extensions, associated with the REGISTRY component 00370 # msidbComponentAttributesRegistryKeyPath = 4 00371 add_data(self.db, "Component", 00372 [("REGISTRY", msilib.gen_uuid(), "TARGETDIR", 4, None, "InstallPath")]) 00373 add_data(self.db, "FeatureComponents", [(default_feature.id, "REGISTRY")]) 00374 self.db.Commit() 00375 00376 def make_msi(self,files): 00377 self.build_database() 00378 try: 00379 if (self.isDebug): 00380 print '(make_msi) :: BEFORE.add_features(), db=(%s)' % (str(self.db)) 00381 self.add_features() 00382 if (self.isDebug): 00383 print '(make_msi) :: BEFORE.add_ui()' 00384 self.add_ui() 00385 if (self.isDebug): 00386 print '(make_msi) :: BEFORE.add_files()' 00387 self._add_files() 00388 self.add_files(files) 00389 if (self.isDebug): 00390 print '(make_msi) :: BEFORE.add_registry()' 00391 self.add_registry() 00392 if (self.isDebug): 00393 print '(make_msi) :: BEFORE.db.Commit()' 00394 self.db.Commit() 00395 finally: 00396 if (self.isDebug): 00397 print '(make_msi) :: BEFORE.[del self.db]' 00398 del self.db 00399 00400
© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved; If you reference this document or any part of this document you must use the citation verbatim (including the link) "© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved."
Notice: This source code contained in this document is NOT open source and is NOT being distributed as open source.
122,241 lines of code and growing...