A bbfreeze Tutorial – Build a Binary Series!

The bbfreeze package also allows us to create binaries, but only on Linux and Windows. It’s just an easy_install away, so if you plan on following along with the examples in the article, you should go get it. The bbfreeze package includes egg support, so it can include egg dependencies in your binary, unlike py2exe. You can also freeze multiple scripts at once, include the Python interpreter and more. According to bbfreeze’s PyPI entry, it’s only been tested with Python 2.4-2.5, so keep that in mind. However, I was able to use it with Python 2.6 with no obvious problems.

Getting Started with bbfreeze

You can use easy_install to download and install bbfreeze or you can just download its source or the egg file directly from the Python Package Index (PyPI). In this article, we’ll try using it on a simple configuration file generator script and we’ll also try it against a lame wxPython program. My test machine was a Windows 7 Home Edition 32-bit laptop with bbfreeze 0.96.5, and Python 2.6.4. Let’s start with the configuration script:

# config_1.py
import configobj

#----------------------------------------------------------------------
def createConfig(configFile):
    """
    Create the configuration file
    """
    config = configobj.ConfigObj()
    inifile = configFile
    config.filename = inifile
    config['server'] = "http://www.google.com"
    config['username'] = "mike"
    config['password'] = "dingbat"
    config['update interval'] = 2
    config.write()
    
#----------------------------------------------------------------------
def getConfig(configFile):
    """
    Open the config file and return a configobj
    """    
    return configobj.ConfigObj(configFile)

def createConfig2(path):
    """
    Create a config file
    """
    config = configobj.ConfigObj()
    config.filename = path
    config["Sony"] = {}
    config["Sony"]["product"] = "Sony PS3"
    config["Sony"]["accessories"] = ['controller', 'eye', 'memory stick']
    config["Sony"]["retail price"] = "$400"
    config.write()

if __name__ == "__main__":
    createConfig2("sampleConfig2.ini")

This script has a couple of functions that are pointless, but we’ll leave them in for illustrative purposes. According to the bbfreeze documentation, we should be able to create a binary with the following string typed into the command line:


bb-freeze config_1.py

This assumes that you have “C:\Python26\Scripts” on your path. If you don’t, you’ll need to type the complete path out (i.e. “C:\Python26\Scripts\bb-freeze config_1.py”). When I ran this, I got an error. Here it is:

Traceback (most recent call last):
  File "C:\Python26\Scripts\bb-freeze-script.py", line 8, in 
    load_entry_point('bbfreeze==0.96.5', 'console_scripts', 'bb-freeze')()
  File "C:\Python26\lib\site-packages\bbfreeze-0.96.5-py2.6-win32.egg\bbfreeze\__init__.py", line 18, in main
    f()
  File "C:\Python26\lib\site-packages\bbfreeze-0.96.5-py2.6-win32.egg\bbfreeze\freezer.py", line 474, in __call__
    self.addModule("encodings.*")
  File "C:\Python26\lib\site-packages\bbfreeze-0.96.5-py2.6-win32.egg\bbfreeze\freezer.py", line 411, in addModule
    self.mf.import_hook(name[:-2], fromlist="*")
  File "C:\Python26\lib\site-packages\bbfreeze-0.96.5-py2.6-win32.egg\bbfreeze\modulegraph\modulegraph.py", line 256, in import_hook
    modules.update(self.ensure_fromlist(m, fromlist))
  File "C:\Python26\lib\site-packages\bbfreeze-0.96.5-py2.6-win32.egg\bbfreeze\modulegraph\modulegraph.py", line 345, in ensure_fromlist
    fromlist.update(self.find_all_submodules(m))
  File "C:\Python26\lib\site-packages\bbfreeze-0.96.5-py2.6-win32.egg\bbfreeze\modulegraph\modulegraph.py", line 369, in find_all_submodules
    for (path, mode, typ) in ifilter(None, imap(moduleInfoForPath, names)):
NameError: global name 'ifilter' is not defined

It would seem that the “modulegraph.py” file is missing an import from the itertools library, which is included with Python. I edited my copy of “modulegraph.py” to include the following line at the top of the file: “from itertools import ifilter”. This got rid of that traceback, but it raised another because “imap” wasn’t defined either. To fix the second error, I changed my import to “from itertools import ifilter,imap” and then it ran with no problems and produced a binary in a “dist” folder along with nine other files (see screenshot below).

bbfreeze_dir.png

Using bbfreeze’s Advanced Configuration

The PyPI page for bbfreeze (which is also its home page) has very little documentation. However, the page does say that the preferred way to use bbfreeze is with little scripts. We’re going to try using creating a binary with the lame wxPython, mentioned earlier. Here’s the wx code:

import wx

########################################################################
class DemoPanel(wx.Panel):
    """"""
    
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        
        labels = ["Name", "Address", "City", "State", "Zip",
                  "Phone", "Email", "Notes"]
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        lbl = wx.StaticText(self, label="Please enter your information here:")
        lbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD))
        mainSizer.Add(lbl, 0, wx.ALL, 5)
        for lbl in labels:
            sizer = self.buildControls(lbl)
            mainSizer.Add(sizer, 1, wx.EXPAND)
        self.SetSizer(mainSizer)
        mainSizer.Layout()
        
    #----------------------------------------------------------------------
    def buildControls(self, label):
        """"""
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        size = (80,40)
        font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD)
        
        lbl = wx.StaticText(self, label=label, size=size)
        lbl.SetFont(font)
        sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
        if label != "Notes":
            txt = wx.TextCtrl(self, name=label)
        else:
            txt = wx.TextCtrl(self, style=wx.TE_MULTILINE, name=label)
        sizer.Add(txt, 1, wx.ALL, 5)
        return sizer
    
    

########################################################################
class DemoFrame(wx.Frame):
    """
    Frame that holds all other widgets
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""        
        wx.Frame.__init__(self, None, wx.ID_ANY, 
                          "cxFreeze Tutorial",
                          size=(600,400)
                          )
        panel = DemoPanel(self)        
        self.Show()
        
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = DemoFrame()
    app.MainLoop()

Now let’s create a simple freezing script!

# bb_setup.py
from bbfreeze import Freezer

f = Freezer(distdir="bb-binary")
f.addScript("sampleApp.py")
f()

First off, we import the Freezer class from the bbfreeze package. Freezer accepts three arguments: a destination folder, an includes iterable and an excludes iterable (i.e. a tuple or list). Just to see how well bbfreeze works with only its defaults, we leave out the includes and excludes tuples/lists. Once you have a Freezer object, you can add your script(s) by calling the Freezer object name’s addScript method. Then you just need to call the object (i.e. f() ). If you do all that, you should end up with a 14.5 MB folder holding 18 files. When I ran the sampleApp.exe file, it ran just fine and was properly themed, however it also had a console screen. To figure out the correct syntax, I used GUI2Exe. Here’s the new code:

# bb_setup2.py
from bbfreeze import Freezer

includes = []
excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
            'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
            'Tkconstants', 'Tkinter']

bbFreeze_Class = Freezer('dist', includes=includes, excludes=excludes)

bbFreeze_Class.addScript("sampleApp.py", gui_only=True)

bbFreeze_Class.use_compression = 0
bbFreeze_Class.include_py = True
bbFreeze_Class()

If you run this, you should end up with a “dist” folder that contains 18 files and is 16.6 MB in size. Notice that we added a second argument to the addScript method: gui_only=True. This makes that annoying console go away. We also set compression to zero (no compression, I think) and include the Python interpreter. Turning on compression only reduced the result down to 14.3 MB though.

The bbfreeze package also handles “recipes” and includes several examples, however they are not documented well either and I couldn’t figure out how to include them with my current examples. Feel free to figure them out on your own!

Wrapping Up

Well, now you should know the basics of using bbfreeze to create binaries from your programs. I hope you found this helpful. The last article in this series will be on GUI2Exe. Look for it soon!