编译python为EXE或者Pyd

2019-09-26  本文已影响0人  Bug2Coder
1、准备工具

1.1 cython、mingw(gcc)并添加到系统环境变量中
1.2 wxpython 模块
缺点:需要python环境和相应的包

# coding:utf-8

import re
import os
import wx
import subprocess
import shutil
import time


class MainWindow(wx.Frame):
    def __init__(self, parent, title, pos, size):
        super(MainWindow, self).__init__(parent, title=title, pos=pos, size=size, style=wx.DEFAULT_FRAME_STYLE)

        self.__comList = []  # combox下拉列表

        # 获取Python路径
        self.GetDefaultPath()

        # 初始化界面
        self.InitUI()

        # 事件绑定
        self.BindEvent()



    def InitUI(self):
        """
        初始化界面
        """
        self.panel = wx.Panel(self)
        self.sizer = wx.GridBagSizer(0, 0)

        self.sbox1 = wx.StaticText(self.panel, wx.ID_ANY, u'python路径:')
        self.sizer.Add(self.sbox1, pos=(0, 0), span=(1, 1), flag=wx.ALL, border=5)
        self.combox = wx.ComboBox(self.panel, wx.ID_ANY, choices=self.__comList, style=wx.CB_READONLY)
        self.m_btnAdd1 = wx.Button(self.panel, wx.ID_ANY, u"添  加")
        self.sizer.Add(self.combox, pos=(1, 0), span=(1, 8), flag=wx.EXPAND | wx.ALL, border=5)
        self.sizer.Add(self.m_btnAdd1, pos=(1, 8), span=(1, 1), flag=wx.ALL, border=5)

        self.sbox2 = wx.StaticText(self.panel, wx.ID_ANY, u'需要打包exe的py文件:')
        self.sizer.Add(self.sbox2, pos=(2, 0), span=(1, 1), flag=wx.ALL, border=5)
        self.m_btnAdd2 = wx.Button(self.panel, wx.ID_ANY, u"添  加")
        self.m_txtCtrl2 = wx.TextCtrl(self.panel, wx.ID_ANY, style=wx.TE_LEFT | wx.TE_READONLY)
        self.sizer.Add(self.m_txtCtrl2, pos=(3, 0), span=(1, 8), flag=wx.EXPAND | wx.ALL, border=5)
        self.sizer.Add(self.m_btnAdd2, pos=(3, 8), span=(1, 1), flag=wx.ALL, border=5)

        self.sbox4 = wx.StaticText(self.panel, wx.ID_ANY, u'需要打包pyd的py文件:')
        self.sizer.Add(self.sbox4, pos=(4, 0), span=(1, 1), flag=wx.ALL, border=5)
        self.m_listBox = wx.ListBox(self.panel, wx.ID_ANY, style=wx.LB_EXTENDED)
        self.sizer.Add(self.m_listBox, pos=(5, 0), span=(0, 9), flag=wx.EXPAND | wx.ALL, border=5)

        self.m_btnImport = wx.Button(self.panel, label=u"添  加")
        self.m_btnDelete = wx.Button(self.panel, label=u"删  除")
        self.m_btnPack = wx.Button(self.panel, label=u"打  包")
        self.sizer.Add(self.m_btnImport, pos=(6, 6), flag=wx.ALL, border=5)
        self.sizer.Add(self.m_btnDelete, pos=(6, 7), flag=wx.ALL, border=5)
        self.sizer.Add(self.m_btnPack, pos=(6, 8), flag=wx.ALL, border=5)

        self.sizer.AddGrowableRow(5)
        self.sizer.AddGrowableCol(2)

        # 状态栏
        self.CreateStatusBar()
        if len(self.__comList) > 0:
            self.getSysInfo(self.__comList[0])
            self.combox.SetValue(self.__comList[0])
            self.SetStatusText(self.sysName + "  python:" + self.pyVersion + "_" + self.pyBit)
        else:
            self.SetStatusText("Welcome...")

        self.panel.SetBackgroundColour(wx.Colour(240, 255, 255))
        self.panel.SetSizerAndFit(self.sizer)
        self.Centre()
        self.Raise()

    def BindEvent(self):
        """
        绑定事件
        """
        self.Bind(wx.EVT_BUTTON, self.onAdd1, self.m_btnAdd1)
        self.Bind(wx.EVT_BUTTON, self.onAdd2, self.m_btnAdd2)
        self.Bind(wx.EVT_BUTTON, self.onImport, self.m_btnImport)
        self.Bind(wx.EVT_BUTTON, self.onDelete, self.m_btnDelete)
        self.Bind(wx.EVT_BUTTON, self.onPack, self.m_btnPack)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.onListboxDoubleClick, self.m_listBox)
        self.Bind(wx.EVT_COMBOBOX, self.comboxSelcet, self.combox)

    def GetDefaultPath(self):
        """
        获取默认的python路径,目前只使用与windows
        """
        path = os.environ['path']
        lst = path.split(';')
        for dir in lst:
            try:
                files = os.listdir(dir)
                for file in files:
                    if file == "python.exe":
                        self.__comList.append(dir)
            except:
                pass

    def getSysInfo(self, pythonPath):
        """
        指定python路径时收集系统信息
        """
        cmd = pythonPath + "\python.exe -c "
        cmd += "\"import platform; print(platform.architecture())\""
        p = subprocess.run(cmd, stdin=subprocess.PIPE, startupinfo=startupinfo,
                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        rtn = p.stdout.decode()
        if '32' in rtn:
            self.pyBit = "32"
        elif "64" in rtn:
            self.pyBit = "64"
        else:
            self.pyBit = ""

        cmd = pythonPath + "\python.exe -c "
        cmd += "\"import platform; print(platform.system())\""
        p = subprocess.run(cmd, stdin=subprocess.PIPE, startupinfo=startupinfo,
                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        rtn = p.stdout.decode()
        if "Windows" in rtn:
            self.sysName = "Windows"
        elif "Linux" in rtn:
            self.sysName = "Linux"
        else:
            self.sysName = ""

        cmd = pythonPath + "\python.exe -c "
        cmd += "\"import platform; print(platform.python_version())\""
        p = subprocess.run(cmd, stdin=subprocess.PIPE, startupinfo=startupinfo,
                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        rtn = p.stdout.decode()
        if rtn is not None:
            self.pyVersion = rtn[:3]
        else:
            self.pyVersion = ""

    def chkCython(self, path):
        '''
        检查是否安装了cython
        '''
        path += "\Scripts"
        files = os.listdir(path)
        for file in files:
            if file == "cython.exe":
                return True
        return False

    def addComboxList(self, path):
        '''
        增加combox的list选项
        '''
        for str in self.__comList:
            if str == path:
                self.combox.SetValue(str)
                return

        self.__comList.append(path)
        self.combox.Append(path)
        self.combox.SetValue(path)

    def comboxSelcet(self, event):
        """
        combox的选择事件
        """
        path = self.combox.GetStringSelection()
        self.getSysInfo(path)
        self.SetStatusText(self.sysName + "  python:" + self.pyVersion + "_" + self.pyBit)

    def onAdd1(self, event):
        """
        添加python.exe路径
        """
        dlg = wx.DirDialog(self, u"选择文件夹", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            pythonPath = dlg.GetPath()
            if pythonPath is not None:
                if self.checkPythonPath(pythonPath):
                    self.addComboxList(pythonPath)
                else:
                    self.SetStatusText(u"添加python.exe目录错误,请重新选择...")
                    dlg.Destroy()
                    return
        dlg.Destroy()
        self.getSysInfo(pythonPath)
        self.SetStatusText(self.sysName + "  python:" + self.pyVersion + "_" + self.pyBit)

    def checkPythonPath(self, pythonPath):
        """
        检查添加的python路径是否正确
        """
        for fp in os.listdir(pythonPath):
            if fp == "python.exe":
                return True
        return False

    def onAdd2(self, event):
        """
        添加打包exe的py文件
        """
        file_wildcard = "python files(*.py)|*.py"
        dlg = wx.FileDialog(self, "", os.getcwd(), wildcard=file_wildcard)
        if dlg.ShowModal() == wx.ID_OK:
            filename = dlg.GetPath()
            if filename is not None:
                self.m_txtCtrl2.SetValue(filename)
                self.SetStatusText(u"添加打包exe的python文件")
        dlg.Destroy()

    def onImport(self, event):
        """
        导入需要打包pyd的py文件
        """
        file_wildcard = "python files(*.py)|*.py"
        dlg = wx.FileDialog(self, "", os.getcwd(), wildcard=file_wildcard, style=wx.FD_MULTIPLE)
        if dlg.ShowModal() == wx.ID_OK:
            filename = dlg.GetPaths()
            num = self.m_listBox.GetCount()
            tmp = filename.copy()
            for name in tmp:
                if self.m_listBox.FindString(name) != wx.NOT_FOUND:
                    filename.remove(name)

            if len(filename):
                self.m_listBox.InsertItems(filename, num)
                self.SetStatusText(u"添加打包pyd的python文件")
        dlg.Destroy()

    def onListboxDoubleClick(self, event):
        """
        双击Listbox元素删除
        """
        listItems = self.m_listBox.GetSelections()
        self.m_listBox.Delete(listItems[0])
        self.SetStatusText(u"删除打包pyd的python文件")

    def onDelete(self, event):
        """
        删除listbox中选中的item
        """
        allItem = self.m_listBox.GetItems()
        listItems = self.m_listBox.GetSelections()
        for i in listItems:
            allItem.remove(self.m_listBox.GetString(i))

        if len(listItems):
            self.m_listBox.Clear()
            self.m_listBox.InsertItems(allItem, 0)
            self.SetStatusText(u"删除打包pyd的python文件")

    def getLibName(self, path):
        """
        得到python的库名(如 python36)
        """
        path += '\libs'
        for fp in os.listdir(path):
            rtn = re.match(r'python...lib', fp)
            if rtn is not None:
                return fp[:len(fp) - 4]

    def modifyWmain(self, filePath):
        """
        修改*.c文件中的wmain-->main
        32位gcc并没有 -municode 选项,不能识别 wmain
        """
        fopen = open(filePath, "r")
        str = ""
        for line in fopen:
            if re.search("wmain\(int", line):
                line = re.sub("wmain", "main", line)
                str += line
            else:
                str += line

        wopen = open(filePath, "w")
        wopen.write(str)
        fopen.close()
        wopen.close()

    def onPack(self, event):
        """
        打包
        """
        self.SetStatusText(u"开始打包...")
        pythonPath = self.combox.GetStringSelection()
        allItem = self.m_listBox.GetItems()

        if pythonPath == "":
            self.SetStatusText(u"未添加python执行路径")
            return
        elif len(allItem):
            if not self.packPydFiles(pythonPath):
                self.SetStatusText(u"Pyd打包完成...")
        elif self.m_txtCtrl2.GetLineText(0):
            if not self.packEXE(pythonPath):
                if not self.copyDLL():
                    self.SetStatusText(u"EXE打包完成...")
        else:
            self.SetStatusText(u"未选择文件...")

    def packEXE(self, pythonPath):
        """
        打包exe
        """
        packFile = self.m_txtCtrl2.GetLineText(0)
        if packFile == "":
            self.SetStatusText(u"未添加打包exe的python文件,跳过exe打包...")
            return False

        mingw = ""
        if self.sysName == "Windows":
            mingw = "mingw"
        else:
            pass  # linux暂时没有实现

        strPath = self.combox.GetStringSelection()
        if not self.chkCython(strPath):
            self.SetStatusText(u"没有安装cython,请安装...")
            return False

        # 生成*.c文件
        pos1 = packFile.rfind('\\')
        pos2 = packFile.rfind(".")
        filePath = packFile[:pos1]
        str = packFile[pos1 + 1:pos2]
        str_c = str + ".c"
        str_exe = str + ".exe"
        cmd = 'cython {} --embed '.format(packFile)
        p = subprocess.run(cmd, stdin=subprocess.PIPE,startupinfo=startupinfo,
                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        rtn = p.returncode
        if rtn == 0:
            self.SetStatusText(u"成功生成.c文件:" + str_c)
        else:
            self.SetStatusText(u"未能生成.c文件:" + str_c)
            return False

        # 生成exe文件
        curPath = os.getcwd()
        os.chdir(filePath)
        if self.pyBit == "64":
            cmd = 'gcc {} -static -mwindows -o {} -municode -DMS_WIN64 -I "{}\include" -L "{}\libs" -l {}'.format(
                str_c, str, pythonPath,
                pythonPath,
                self.getLibName(
                    pythonPath))
        elif self.pyBit == "32":
            if self.pyVersion[0] == "3":
                self.modifyWmain(filePath + "\\" + str_c)  # 修改wmain
            cmd = 'gcc {} -static -mwindows -m32 -o {} -I "{}\include" -L "{}\libs" -l {}'.format(str_c, str,
                                                                                                  pythonPath,
                                                                                                  pythonPath,
                                                                                                  self.getLibName(
                                                                                                      pythonPath))
        rtn2 = subprocess.run(cmd, stdin=subprocess.PIPE,startupinfo=startupinfo,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        rtn2 = rtn2.returncode
        res = subprocess.run("cmd /C del {}\{}".format(curPath, str_c), stdin=subprocess.PIPE,startupinfo=startupinfo,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        if rtn2 == 0:
            self.SetStatusText(u"成功生成EXE文件:" + str_exe)
        else:
            self.SetStatusText(u"未能生成EXE文件:" + str_exe)
            os.chdir(curPath)
            return False

        os.chdir(curPath)
        return True

    def packPydFiles(self, pythonPath):
        """
        打包pyd文件
        """
        curPath = os.getcwd()
        nItem = self.m_listBox.GetCount()
        for i in range(nItem):
            str = self.m_listBox.GetString(i)
            pos1 = str.rfind('\\')
            pos2 = str.rfind(".")
            filePath = str[:pos1]
            fileName = str[pos1 + 1:pos2]

            # 生成*.c文件
            cmd = 'cython {0}'.format(str)

            rtn2 = subprocess.run(cmd, stdin=subprocess.PIPE,startupinfo=startupinfo,
                                  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            rtn = rtn2.returncode
            str_c = fileName + ".c"
            if rtn == 0:
                self.SetStatusText(u"成功生成.c文件:" + str_c)
            else:
                self.SetStatusText(u"未能生成.c文件:" + str_c)

            # 生成*.pyd文件
            mingw = ""
            if self.sysName == "Windows":
                mingw = "mingw"
            elif self.sysName == "Linux":
                pass  # linux暂时没有实现

            str_pyd = fileName + ".pyd"
            os.chdir(filePath)
            if self.pyBit == "64":
                cmd = 'gcc {} -o {} -shared -DMS_WIN64 -I "{}\include" -L "{}\libs" -l {}'.format(str_c, str_pyd,
                                                                                                  pythonPath,
                                                                                                  pythonPath,
                                                                                                  self.getLibName(
                                                                                                      pythonPath))
            elif self.pyBit == "32":

                cmd = 'gcc {} -m32 -o {} -shared -I "{}\include" -L "{}\libs" -l {}'.format(str_c, str_pyd, pythonPath,
                                                                                            pythonPath,
                                                                                            self.getLibName(
                                                                                                pythonPath))
            else:
                cmd = ""

            rtn2 = subprocess.run(cmd, stdin=subprocess.PIPE,startupinfo=startupinfo,
                                  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            rtn = rtn2.returncode
            res = subprocess.run("cmd /C del {}\{}".format(curPath, str_c), stdin=subprocess.PIPE,startupinfo=startupinfo,
                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            if rtn == 0:
                self.SetStatusText(u"成功生成pyd文件:" + str_pyd)
            else:
                self.SetStatusText(u"未能生成pyd文件:" + str_pyd)
                os.chdir(curPath)
                return False

        os.chdir(curPath)
        return True

    def copyDLL(self):
        """
        复制DLL到exe生成目录
        """
        fileName = self.m_txtCtrl2.GetLineText(0)
        pos = fileName.rfind('\\')
        filePath = fileName[:pos]

        pythonPath = self.combox.GetStringSelection()
        dllName = "python" + self.pyVersion[0] + self.pyVersion[2] + ".dll"
        flag = False
        for fp in os.listdir(pythonPath):
            if fp == dllName:
                flag = True
                break

        if not flag:
            self.SetStatusText(pythonPath + u"下不存在" + dllName)
            return False

        pythonPath += "\\" + dllName
        shutil.copy(pythonPath, filePath)
        return True


if __name__ == "__main__":
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    startupinfo.wShowWindow = subprocess.SW_HIDE
    app = wx.App()
    frm = MainWindow(None, "程序打包", (300, 200), (700, 500))
    frm.Show()
    app.MainLoop()

知识点:
1.运行系统命令subprocess会出现黑窗口:
解决办法:

添加如下信息
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
subprocess.run(cmd, stdin=subprocess.PIPE,startupinfo=startupinfo,
                                  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
上一篇下一篇

猜你喜欢

热点阅读