cxfreeze打包python程序的方法說明(生成安裝包,實現桌面快捷方式、刪除快捷方式)


一、cxfreeze基礎

 1、cxfreeze功能

python代碼文件轉exe方法有三種,分別是cx_freeze,py2exe,PyInstaller,這三種方式各有千秋,本人只用過py2exe和cxfreeze,這里重點說明cxfreeze。

 

2、安裝包下載地址

 https://sourceforge.net/projects/cx-freeze/files/

 

3、cxfree的官方說明文檔

 http://cx-freeze.readthedocs.io/en/latest/distutils.html

 

二、cxfreeze使用方法

1、cxfreeze命令方法

cxfreeze etax.py --target-dir out/      #把etax.py 打包成etax.exe,放在out目錄下 

 

2、編寫cxsetup.py編譯腳本,然后用py去執行。

來一個簡單的需求:編譯etax.py生成test.exe文件。

a、步驟1,先編寫一個cxsetup.py腳本文件

#coding=utf-8
#cx
setup.py代碼
from cx_Freeze  import setup, Executable
setup(
    name="test",
    version="1.0",
    description="Test application",
    author="zhongtang",
    executables=[Executable("etax.py")]
)

可以看到,cxsetup.py其實是一個py程序,該程序調用了cx_Freeze 包中的setup、Executable類。

然后用python執行cxsetup.py,就可以實現編譯exe。

另外補充一點,cxsetup.py可以隨意起名,默認都叫xxsetup.py

 

編譯后的文件屬性如下:

 

 

 b、步驟2,執行py命令

#build方式打包成exe文件,可以脫離python環境運行

python cxsetup.py build 

 

#bdist_msi方式可以打包成windows下msi格式的安裝包文件

python cxsetup.py bdist_msi 

 

三、cxsetup.py程序的進階寫法

還是以一個實例說明,需求如下:

1、et是一個基於wxpython編寫的圖形界面的小程序

2、et中使用了ini配置文件,文件名為et.ini

3、et中使用了PIL類,並使用圖片文件et.jpg

4、et程序一共包含4個文件,主程序名為eTMain.py

5、打包py成exe,脫離python環境運行

6、生成windows下的msi安裝包,該安裝包運行后會安裝桌面快捷方式、開始菜單快捷方式、刪除程序的快捷方式,並且開始菜單有子目錄。

 

 上cxsetup.py代碼

# !/usr/bin/python
#
coding=utf-8
#
 create by :joshua zou 2016.7.23

import sys
import traceback
from cx_Freeze  import setup, Executable
import msilib

#  Dependencies are automatically detected, but it might need fine tuning.

# 中文需要顯式用gbk方式編碼
product_name = u ' 異體 '.encode( ' gbk ')
unproduct_name = u ' 卸載異體 '.encode( ' gbk ')
product_desc = u " 異體客戶端程序 Ver1.0 ".encode( " gbk ")

# uuid叫通用唯一識別碼,后面再卸載快捷方式中要用到
product_code = msilib.gen_uuid()
# 主程序手動命名
target_name=  ' etMain.exe '


build_exe_options = {
     " include_files ":[ " et.ini ", " et.jpg ",'data'],    
    # 包含外圍的ini、jpg文件,以及data目錄下所有文件,以上所有的文件路徑都是相對於cxsetup.py的路徑。
     " packages ": [ " os ", " wx "],                 # 包含用到的包
     " includes ": [ " PIL ", " traceback "], 
     " excludes ": [ " tkinter "],                 # 提出wx里tkinter包
     " path ": sys.path,                        # 指定上述的尋找路徑
     " icon "" et.ico "                         # 指定ico文件
};

# 快捷方式表,這里定義了三個快捷方式
shortcut_table = [
     
      # 1、桌面快捷方式
    ( " DesktopShortcut ",            #  Shortcut
      " DesktopFolder ",              #  Directory_ ,必須在Directory表中
     product_name,                 #  Name
      " TARGETDIR ",                  #  Component_,必須在Component表中
      " [TARGETDIR] "+target_name,    #  Target
     None,                         #  Arguments
     product_desc,                 #  Description
     None,                         #  Hotkey
     None,                         #  Icon
     None,                         #  IconIndex
     None,                         #  ShowCmd
      ' TARGETDIR '                   #  WkDir
     ),
    
     # 2、開始菜單快捷方式
    ( " StartupShortcut ",            #  Shortcut
      " MenuDir ",                    #  Directory_
     product_name,                 #  Name
      " TARGETDIR ",                  #  Component_
      " [TARGETDIR] "+target_name,    #  Target
     None,                         #  Arguments
     product_desc,                 #  Description
     None,                         #  Hotkey
     None,                         #  Icon
     None,                         #  IconIndex
     None,                         #  ShowCmd
      ' TARGETDIR '                   #  WkDir
     ),
    
     #3 、程序卸載快捷方式
    ( " UniShortcut ",               #  Shortcut
      " MenuDir ",                   #  Directory_
     unproduct_name,              #  Name
      " TARGETDIR ",                 #  Component_
      " [System64Folder]msiexec.exe ",   #  Target
     r " /x "+product_code,          #  Arguments
     product_desc,                #  Description
     None,                        #  Hotkey
     None,                        #  Icon
     None,                        #  IconIndex
     None,                        #  ShowCmd
      ' TARGETDIR '                  #  WkDir
     )      
    ]


# 手動建設的目錄,在這里定義。
'''
自定義目錄說明:
==============
1、3個字段分別為 Directory,Directory_Parent,DefaultDir
2、字段1指目錄名,可以隨意命名,並在后面直接使用
3、字段2是指字段1的上級目錄,上級目錄本身也是需要預先定義,除了某些系統自動定義的目錄,譬如桌面快捷方式中使用DesktopFolder
參考網址 https://msdn.microsoft.com/en-us/library/aa372452(v=vs.85).aspx
'''
directories = [
     (  " ProgramMenuFolder ", " TARGETDIR ", " . " ),
     (  " MenuDir "" ProgramMenuFolder ", product_name)
     ]

#  Now create the table dictionary
#
 也可把directories放到data里。
'''
快捷方式說明:
============
1、windows的msi安裝包文件,本身都帶一個install database,包含很多表(用一個Orca軟件可以看到)。
2、下面的 Directory、Shortcut都是msi數據庫中的表,所以冒號前面的名字是固定的(貌似大小寫是區分的)。
3、data節點其實是擴展很多自定義的東西,譬如前面的directories的配置,其實cxfreeze中代碼的內容之一,就是把相關配置數據寫入到msi數據庫的對應表中
參考網址:https://msdn.microsoft.com/en-us/library/aa367441(v=vs.85).aspx
'''
msi_data = { # "Directory":directories ,
             " Shortcut ": shortcut_table 
          }

#  Change some default MSI options and specify the use of the above defined tables
#
注意product_code是我擴展的,現有的官網cx_freeze不支持該參數,為此簡單修改了cx_freeze包的代碼,后面貼上修改的代碼。
bdist_msi_options = {  ' data ': msi_data,
                       ' upgrade_code '' {9f21e33d-48f7-cf34-33e9-efcfd80eed10} ',
                       ' add_to_path ': False,
                       ' directories ': directories,
                       ' product_code ': product_code,
                       ' initial_target_dir ': r ' [ProgramFilesFolder]\%s ' % (product_name)}
                      

#  GUI applications require a different base on Windows (the default is for a
#
 console application).
base = None;
if sys.platform ==  " win32 ":
     base =  " Win32GUI "

# 簡易方式定義快捷方式,放到Executeable()里。
#
shortcutName = "AppName",
#
shortcutDir = "ProgramMenuFolder" 
setup(  name =  " et ",
        author= ' et china corp ',
        version =  " 1.0 ",
        description = product_desc.decode( ' gbk '),
        options = { " build_exe ": build_exe_options,
                    " bdist_msi ": bdist_msi_options},
        executables = [Executable( " etMain.py ",
                                  targetName= target_name,
                                  compress = True, 
                                  base=base)
                       ]) 

 

四、補充說明

1、有關windows install msi 文件

可以去microsoft的官網學習學習,https://msdn.microsoft.com/en-us/library/aa372860(v=vs.85).aspx

 

2、Orca編輯工具

查看修改msi文件數據庫表的工具,Orca(msi編輯工具) 4.5.6 中文綠色版 。

絕對堪稱神器,貼個圖片,這玩意太棒了(本文很多寫法就是仿照python2.7的安裝文件的數據,結合cxfree代碼琢磨出來的)。

 

 

3、擴展的cxfreeze代碼

前文在cxsetup.exe中我提到自定義了product_code參數,這個參數在官方版本的cxfreeze是不支持的(官方版本的productcode是直接寫死的代碼msilib.gen_uuid())。

所以擴展product_code配置的目的,就是因為在卸載Shortcut,需要用到 msiexec.exe /x {productcode}。

 

修改原理:

將 msilib.gen_uuid()放到cxsetup.py中,並作為product_code參數傳給cxfreeze。

在cxfreeze中判斷product_code參數是否定義,沒定義則默認取msilib.gen_uuid(),有定義則使用定義值。

修改點:

cx_Free/windist.py文件。

修改點1、

class bdist_msi(distutils.command.bdist_msi.bdist_msi):
    user_options = distutils.command.bdist_msi.bdist_msi.user_options + [
        ('add-to-path=', None, 'add target dir to PATH environment variable'),
        ('upgrade-code=', None, 'upgrade code to use'),
        ('initial-target-dir=', None, 'initial target directory'),
        ('target-name=', None, 'name of the file to create'),
        ('directories=', None, 'list of 3-tuples of directories to create'),
        ('data=', None, 'dictionary of data indexed by table name'),
        # add by joshua zou 2016.07.23
        ('product-code=', None, 'product code to use')
    ]

 修改點2、

    def finalize_options(self):
        distutils.command.bdist_msi.bdist_msi.finalize_options(self)
        name = self.distribution.get_name()
        fullname = self.distribution.get_fullname()
        if self.initial_target_dir is None:
            if distutils.util.get_platform() == "win-amd64":
                programFilesFolder = "ProgramFiles64Folder"
            else:
                programFilesFolder = "ProgramFilesFolder"
            self.initial_target_dir = r"[%s]\%s" % (programFilesFolder, name)
        if self.add_to_path is None:
            self.add_to_path = False
        if self.target_name is None:
            self.target_name = fullname
        if not self.target_name.lower().endswith(".msi"):
            platform = distutils.util.get_platform().replace("win-", "")
            self.target_name = "%s-%s.msi" % (self.target_name, platform)
        if not os.path.isabs(self.target_name):
            self.target_name = os.path.join(self.dist_dir, self.target_name)
        if self.directories is None:
            self.directories = []
        if self.data is None:
            self.data = {}
        # add by joshua zou 2016.7
        if self.product_code is None:
            self.product_code = msilib.gen_uuid()

修改點3、

  def initialize_options(self):
        distutils.command.bdist_msi.bdist_msi.initialize_options(self)
        self.upgrade_code = None
        self.add_to_path = None
        self.initial_target_dir = None
        self.target_name = None
        self.directories = None
        self.data = None
        # add by joshua zou 2016.7
        self.product_code=None

代碼點4、

    def run(self):
        if not self.skip_build:
            self.run_command('build')
        install = self.reinitialize_command('install', reinit_subcommands = 1)
        install.prefix = self.bdist_dir
        install.skip_build = self.skip_build
        install.warn_dir = 0
        distutils.log.info("installing to %s", self.bdist_dir)
        install.ensure_finalized()
        install.run()
        self.mkpath(self.dist_dir)
        fullname = self.distribution.get_fullname()
        if os.path.exists(self.target_name):
            os.unlink(self.target_name)
        metadata = self.distribution.metadata
        author = metadata.author or metadata.maintainer or "UNKNOWN"
        version = metadata.get_version()
        sversion = "%d.%d.%d" % \
                distutils.version.StrictVersion(version).version
        '''
        modified by joshua zou 2016.7
        self.db = msilib.init_database(self.target_name, msilib.schema,
                self.distribution.metadata.name, msilib.gen_uuid(), sversion,
                author)
        '''
        self.db = msilib.init_database(self.target_name, msilib.schema,
                        self.distribution.metadata.name, self.product_code, sversion,
                        author)       
        msilib.add_tables(self.db, msilib.sequence)

 

完整源碼

import distutils.command.bdist_msi
import distutils.errors
import distutils.util
import msilib
import os

__all__ = [  " bdist_msi " ]

#  force the remove existing products action to happen first since Windows
#
 installer appears to be braindead and doesn't handle files shared between
#
 different "products" very well
sequence = msilib.sequence.InstallExecuteSequence

for index, info  in enumerate(sequence):
     if info[0] ==  ' RemoveExistingProducts ':
        sequence[index] = (info[0], info[1], 1450)


class bdist_msi(distutils.command.bdist_msi.bdist_msi):
    user_options = distutils.command.bdist_msi.bdist_msi.user_options + [
        ( ' add-to-path= ', None,  ' add target dir to PATH environment variable '),
        ( ' upgrade-code= ', None,  ' upgrade code to use '),
        ( ' initial-target-dir= ', None,  ' initial target directory '),
        ( ' target-name= ', None,  ' name of the file to create '),
        ( ' directories= ', None,  ' list of 3-tuples of directories to create '),
        ( ' data= ', None,  ' dictionary of data indexed by table name '),
         #  add by joshua zou 2016.07.23
        ( ' product-code= ', None,  ' product code to use ')
    ]
    x = y = 50
    width = 370
    height = 300
    title =  " [ProductName] Setup "
    modeless = 1
    modal = 3

     def add_config(self, fullname):
         if self.add_to_path:
            msilib.add_data(self.db,  ' Environment ',
                    [( " E_PATH "" Path ", r " [~];[TARGETDIR] "" TARGETDIR ")])
         if self.directories:
            msilib.add_data(self.db,  " Directory ", self.directories)
        msilib.add_data(self.db,  ' CustomAction ',
                [( " A_SET_TARGET_DIR ", 256 + 51,  " TARGETDIR ",
                        self.initial_target_dir)])
        msilib.add_data(self.db,  ' InstallExecuteSequence ',
                [( " A_SET_TARGET_DIR "' TARGETDIR="" ', 401)])
        msilib.add_data(self.db,  ' InstallUISequence ',
                [( " PrepareDlg ", None, 140),
                 ( " A_SET_TARGET_DIR "' TARGETDIR="" ', 401),
                 ( " SelectDirectoryDlg "" not Installed ", 1230),
                 ( " MaintenanceTypeDlg ",
                         " Installed and not Resume and not Preselected ", 1250),
                 ( " ProgressDlg ", None, 1280)
                ])
         for index, executable  in enumerate(self.distribution.executables):
             if executable.shortcutName  is  not None \
                     and executable.shortcutDir  is  not None:
                baseName = os.path.basename(executable.targetName)
                msilib.add_data(self.db,  " Shortcut ",
                        [( " S_APP_%s " % index, executable.shortcutDir,
                                executable.shortcutName,  " TARGETDIR ",
                                 " [TARGETDIR]%s " % baseName, None, None, None,
                                None, None, None, None)])
         for tableName, data  in self.data.items():
            msilib.add_data(self.db, tableName, data)

     def add_cancel_dialog(self):
        dialog = msilib.Dialog(self.db,  " CancelDlg ", 50, 10, 260, 85, 3,
                self.title,  " No "" No "" No ")
        dialog.text( " Text ", 48, 15, 194, 30, 3,
                 " Are you sure you want to cancel [ProductName] installation? ")
        button = dialog.pushbutton( " Yes ", 72, 57, 56, 17, 3,  " Yes "" No ")
        button.event( " EndDialog "" Exit ")
        button = dialog.pushbutton( " No ", 132, 57, 56, 17, 3,  " No "" Yes ")
        button.event( " EndDialog "" Return ")

     def add_error_dialog(self):
        dialog = msilib.Dialog(self.db,  " ErrorDlg ", 50, 10, 330, 101, 65543,
                self.title,  " ErrorText ", None, None)
        dialog.text( " ErrorText ", 50, 9, 280, 48, 3,  "")
         for text, x  in [( " No ", 120), ( " Yes ", 240), ( " Abort ", 0),
                ( " Cancel ", 42), ( " Ignore ", 81), ( " Ok ", 159), ( " Retry ", 198)]:
            button = dialog.pushbutton(text[0], x, 72, 81, 21, 3, text, None)
            button.event( " EndDialog "" Error%s " % text)

     def add_exit_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,  " ExitDialog ",
                self.x, self.y, self.width, self.height, self.modal,
                self.title,  " Finish "" Finish "" Finish ")
        dialog.title( " Completing the [ProductName] installer ")
        dialog.back( " < Back "" Finish ", active = False)
        dialog.cancel( " Cancel "" Back ", active = False)
        dialog.text( " Description ", 15, 235, 320, 20, 0x30003,
                 " Click the Finish button to exit the installer. ")
        button = dialog.next( " Finish "" Cancel ", name =  " Finish ")
        button.event( " EndDialog "" Return ")

     def add_fatal_error_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,  " FatalError ",
                self.x, self.y, self.width, self.height, self.modal,
                self.title,  " Finish "" Finish "" Finish ")
        dialog.title( " [ProductName] installer ended prematurely ")
        dialog.back( " < Back "" Finish ", active = False)
        dialog.cancel( " Cancel "" Back ", active = False)
        dialog.text( " Description1 ", 15, 70, 320, 80, 0x30003,
                 " [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. ")
        dialog.text( " Description2 ", 15, 155, 320, 20, 0x30003,
                 " Click the Finish button to exit the installer. ")
        button = dialog.next( " Finish "" Cancel ", name =  " Finish ")
        button.event( " EndDialog "" Exit ")

     def add_files(self):
        db = self.db
        cab = msilib.CAB( " distfiles ")
        f = msilib.Feature(db,  " default "" Default Feature "" Everything ", 1,
                directory= " TARGETDIR ")
        f.set_current()
        rootdir = os.path.abspath(self.bdist_dir)
        root = msilib.Directory(db, cab, None, rootdir,  " TARGETDIR ",
                 " SourceDir ")
        db.Commit()
        todo = [root]
         while todo:
            dir = todo.pop()
             for file  in os.listdir(dir.absolute):
                 if os.path.isdir(os.path.join(dir.absolute, file)):
                    newDir = msilib.Directory(db, cab, dir, file, file,
                             " %s|%s " % (dir.make_short(file), file))
                    todo.append(newDir)
                 else:
                    dir.add_file(file)
        cab.commit(db)

     def add_files_in_use_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,  " FilesInUse ",
                self.x, self.y, self.width, self.height, 19, self.title,
                 " Retry "" Retry "" Retry ", bitmap = False)
        dialog.text( " Title ", 15, 6, 200, 15, 0x30003,
                r " {\DlgFontBold8}Files in Use ")
        dialog.text( " Description ", 20, 23, 280, 20, 0x30003,
                 " Some files that need to be updated are currently in use. ")
        dialog.text( " Text ", 20, 55, 330, 50, 3,
                 " 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. ")
        dialog.control( " List "" ListBox ", 20, 107, 330, 130, 7,
                 " FileInUseProcess ", None, None, None)
        button = dialog.back( " Exit "" Ignore ", name =  " Exit ")
        button.event( " EndDialog "" Exit ")
        button = dialog.next( " Ignore "" Retry ", name =  " Ignore ")
        button.event( " EndDialog "" Ignore ")
        button = dialog.cancel( " Retry "" Exit ", name =  " Retry ")
        button.event( " EndDialog "" Retry ")

     def add_maintenance_type_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,
                 " MaintenanceTypeDlg ", self.x, self.y, self.width, self.height,
                self.modal, self.title,  " Next "" Next "" Cancel ")
        dialog.title( " Welcome to the [ProductName] Setup Wizard ")
        dialog.text( " BodyText ", 15, 63, 330, 42, 3,
                 " Select whether you want to repair or remove [ProductName]. ")
        group = dialog.radiogroup( " RepairRadioGroup ", 15, 108, 330, 60, 3,
                 " MaintenanceForm_Action """" Next ")
        group.add( " Repair ", 0, 18, 300, 17,  " &Repair [ProductName] ")
        group.add( " Remove ", 0, 36, 300, 17,  " Re&move [ProductName] ")
        dialog.back( " < Back ", None, active = False)
        button = dialog.next( " Finish "" Cancel ")
        button.event( " [REINSTALL] "" ALL ",
                 ' MaintenanceForm_Action="Repair" ', 5)
        button.event( " [Progress1] "" Repairing ",
                 ' MaintenanceForm_Action="Repair" ', 6)
        button.event( " [Progress2] "" repairs ",
                 ' MaintenanceForm_Action="Repair" ', 7)
        button.event( " Reinstall "" ALL ",
                 ' MaintenanceForm_Action="Repair" ', 8)
        button.event( " [REMOVE] "" ALL ",
                 ' MaintenanceForm_Action="Remove" ', 11)
        button.event( " [Progress1] "" Removing ",
                 ' MaintenanceForm_Action="Remove" ', 12)
        button.event( " [Progress2] "" removes ",
                 ' MaintenanceForm_Action="Remove" ', 13)
        button.event( " Remove "" ALL ",
                 ' MaintenanceForm_Action="Remove" ', 14)
        button.event( " EndDialog "" Return ",
                 ' MaintenanceForm_Action<>"Change" ', 20)
        button = dialog.cancel( " Cancel "" RepairRadioGroup ")
        button.event( " SpawnDialog "" CancelDlg ")

     def add_prepare_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,  " PrepareDlg ",
                self.x, self.y, self.width, self.height, self.modeless,
                self.title,  " Cancel "" Cancel "" Cancel ")
        dialog.text( " Description ", 15, 70, 320, 40, 0x30003,
                 " Please wait while the installer prepares to guide you through "
                 " the installation. ")
        dialog.title( " Welcome to the [ProductName] installer ")
        text = dialog.text( " ActionText ", 15, 110, 320, 20, 0x30003,
                 " Pondering... ")
        text.mapping( " ActionText "" Text ")
        text = dialog.text( " ActionData ", 15, 135, 320, 30, 0x30003, None)
        text.mapping( " ActionData "" Text ")
        dialog.back( " Back ", None, active = False)
        dialog.next( " Next ", None, active = False)
        button = dialog.cancel( " Cancel ", None)
        button.event( " SpawnDialog "" CancelDlg ")

     def add_progress_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,  " ProgressDlg ",
                self.x, self.y, self.width, self.height, self.modeless,
                self.title,  " Cancel "" Cancel "" Cancel ", bitmap = False)
        dialog.text( " Title ", 20, 15, 200, 15, 0x30003,
                r " {\DlgFontBold8}[Progress1] [ProductName] ")
        dialog.text( " Text ", 35, 65, 300, 30, 3,
                 " Please wait while the installer [Progress2] [ProductName]. ")
        dialog.text( " StatusLabel ", 35, 100 ,35, 20, 3,  " Status: ")
        text = dialog.text( " ActionText ", 70, 100, self.width - 70, 20, 3,
                 " Pondering... ")
        text.mapping( " ActionText "" Text ")
        control = dialog.control( " ProgressBar "" ProgressBar ", 35, 120, 300,
                10, 65537, None,  " Progress done ", None, None)
        control.mapping( " SetProgress "" Progress ")
        dialog.back( " < Back "" Next ", active = False)
        dialog.next( " Next > "" Cancel ", active = False)
        button = dialog.cancel( " Cancel "" Back ")
        button.event( " SpawnDialog "" CancelDlg ")

     def add_properties(self):
        metadata = self.distribution.metadata
        props = [
                ( ' DistVersion ', metadata.get_version()),
                ( ' DefaultUIFont '' DlgFont8 '),
                ( ' ErrorDialog '' ErrorDlg '),
                ( ' Progress1 '' Install '),
                ( ' Progress2 '' installs '),
                ( ' MaintenanceForm_Action '' Repair '),
                ( ' ALLUSERS '' 1 ')
        ]
        email = metadata.author_email  or metadata.maintainer_email
         if email:
            props.append(( " ARPCONTACT ", email))
         if metadata.url:
            props.append(( " ARPURLINFOABOUT ", metadata.url))
         if self.upgrade_code  is  not None:
            props.append(( " UpgradeCode ", self.upgrade_code))    
        msilib.add_data(self.db,  ' Property ', props)

     def add_select_directory_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,
                 " SelectDirectoryDlg ", self.x, self.y, self.width, self.height,
                self.modal, self.title,  " Next "" Next "" Cancel ")
        dialog.title( " Select destination directory ")
        dialog.back( " < Back ", None, active = False)
        button = dialog.next( " Next > "" Cancel ")
        button.event( " SetTargetPath "" TARGETDIR ", ordering = 1)
        button.event( " SpawnWaitDialog "" WaitForCostingDlg ", ordering = 2)
        button.event( " EndDialog "" Return ", ordering = 3)
        button = dialog.cancel( " Cancel "" DirectoryCombo ")
        button.event( " SpawnDialog "" CancelDlg ")
        dialog.control( " DirectoryCombo "" DirectoryCombo ", 15, 70, 272, 80,
                393219,  " TARGETDIR ", None,  " DirectoryList ", None)
        dialog.control( " DirectoryList "" DirectoryList ", 15, 90, 308, 136, 3,
                 " TARGETDIR ", None,  " PathEdit ", None)
        dialog.control( " PathEdit "" PathEdit ", 15, 230, 306, 16, 3,
                 " TARGETDIR ", None,  " Next ", None)
        button = dialog.pushbutton( " Up ", 306, 70, 18, 18, 3,  " Up ", None)
        button.event( " DirectoryListUp "" 0 ")
        button = dialog.pushbutton( " NewDir ", 324, 70, 30, 18, 3,  " New ", None)
        button.event( " DirectoryListNew "" 0 ")

     def add_text_styles(self):
        msilib.add_data(self.db,  ' TextStyle ',
                [( " DlgFont8 "" Tahoma ", 9, None, 0),
                 ( " DlgFontBold8 "" Tahoma ", 8, None, 1),
                 ( " VerdanaBold10 "" Verdana ", 10, None, 1),
                 ( " VerdanaRed9 "" Verdana ", 9, 255, 0)
                ])

     def add_ui(self):
        self.add_text_styles()
        self.add_error_dialog()
        self.add_fatal_error_dialog()
        self.add_cancel_dialog()
        self.add_exit_dialog()
        self.add_user_exit_dialog()
        self.add_files_in_use_dialog()
        self.add_wait_for_costing_dialog()
        self.add_prepare_dialog()
        self.add_select_directory_dialog()
        self.add_progress_dialog()
        self.add_maintenance_type_dialog()

     def add_upgrade_config(self, sversion):
         if self.upgrade_code  is  not None:
            msilib.add_data(self.db,  ' Upgrade ',
                    [(self.upgrade_code, None, sversion, None, 513, None,
                             " REMOVEOLDVERSION "),
                     (self.upgrade_code, sversion, None, None, 257, None,
                             " REMOVENEWVERSION ")
                    ])

     def add_user_exit_dialog(self):
        dialog = distutils.command.bdist_msi.PyDialog(self.db,  " UserExit ",
                self.x, self.y, self.width, self.height, self.modal,
                self.title,  " Finish "" Finish "" Finish ")
        dialog.title( " [ProductName] installer was interrupted ")
        dialog.back( " < Back "" Finish ", active = False)
        dialog.cancel( " Cancel "" Back ", active = False)
        dialog.text( " Description1 ", 15, 70, 320, 80, 0x30003,
                 " [ProductName] setup was interrupted. Your system has not  "
                 " been modified. To install this program at a later time,  "
                 " please run the installation again. ")
        dialog.text( " Description2 ", 15, 155, 320, 20, 0x30003,
                 " Click the Finish button to exit the installer. ")
        button = dialog.next( " Finish "" Cancel ", name =  " Finish ")
        button.event( " EndDialog "" Exit ")

     def add_wait_for_costing_dialog(self):
        dialog = msilib.Dialog(self.db,  " WaitForCostingDlg ", 50, 10, 260, 85,
                self.modal, self.title,  " Return "" Return "" Return ")
        dialog.text( " Text ", 48, 15, 194, 30, 3,
                 " Please wait while the installer finishes determining your  "
                 " disk space requirements. ")
        button = dialog.pushbutton( " Return ", 102, 57, 56, 17, 3,  " Return ",
                None)
        button.event( " EndDialog "" Exit ")

     def finalize_options(self):
        distutils.command.bdist_msi.bdist_msi.finalize_options(self)
        name = self.distribution.get_name()
        fullname = self.distribution.get_fullname()
         if self.initial_target_dir  is None:
             if distutils.util.get_platform() ==  " win-amd64 ":
                programFilesFolder =  " ProgramFiles64Folder "
             else:
                programFilesFolder =  " ProgramFilesFolder "
            self.initial_target_dir = r " [%s]\%s " % (programFilesFolder, name)
         if self.add_to_path  is None:
            self.add_to_path = False
         if self.target_name  is None:
            self.target_name = fullname
         if  not self.target_name.lower().endswith( " .msi "):
            platform = distutils.util.get_platform().replace( " win- """)
            self.target_name =  " %s-%s.msi " % (self.target_name, platform)
         if  not os.path.isabs(self.target_name):
            self.target_name = os.path.join(self.dist_dir, self.target_name)
         if self.directories  is None:
            self.directories = []
         if self.data  is None:
            self.data = {}
         #  add by joshua zou 2016.7
         if self.product_code  is None:
            self.product_code = msilib.gen_uuid()

     def initialize_options(self):
        distutils.command.bdist_msi.bdist_msi.initialize_options(self)
        self.upgrade_code = None
        self.add_to_path = None
        self.initial_target_dir = None
        self.target_name = None
        self.directories = None
        self.data = None
         #  add by joshua zou 2016.7
        self.product_code=None
        
     def run(self):
         if  not self.skip_build:
            self.run_command( ' build ')
        install = self.reinitialize_command( ' install ', reinit_subcommands = 1)
        install.prefix = self.bdist_dir
        install.skip_build = self.skip_build
        install.warn_dir = 0
        distutils.log.info( " installing to %s ", self.bdist_dir)
        install.ensure_finalized()
        install.run()
        self.mkpath(self.dist_dir)
        fullname = self.distribution.get_fullname()
         if os.path.exists(self.target_name):
            os.unlink(self.target_name)
        metadata = self.distribution.metadata
        author = metadata.author  or metadata.maintainer  or  " UNKNOWN "
        version = metadata.get_version()
        sversion =  " %d.%d.%d " % \
                distutils.version.StrictVersion(version).version
         '''
        modified by joshua zou 2016.7
        self.db = msilib.init_database(self.target_name, msilib.schema,
                self.distribution.metadata.name, msilib.gen_uuid(), sversion,
                author)
        
'''
        self.db = msilib.init_database(self.target_name, msilib.schema,
                        self.distribution.metadata.name, self.product_code, sversion,
                        author)        
        msilib.add_tables(self.db, msilib.sequence)
        self.add_properties()
        self.add_config(fullname)
        self.add_upgrade_config(sversion)
        self.add_ui()
        self.add_files()
        self.db.Commit()
         if  not self.keep_temp:
            distutils.dir_util.remove_tree(self.bdist_dir,
                    dry_run = self.dry_run)
View Code

 

 

五、總結

至此,cxfreeze的用法基本全了,更深入的用法,建議大家去閱讀cxfreeze的源碼。

學習python,個人覺得有幾點非常關鍵:

一、查看官方幫助文檔,二、閱讀源碼,三、借助google(百度上資料太少,而且相對不准確)

 

以上。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM