msiMaker.py

Go to the documentation of this file.
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...