Search code examples
pythontkintercx-freeze

How can I create multiple msi installers for the same script with different parameters using cx_Freeze?


I want to create many msi installers from one tkinter script with different parameters to distribute it to different customers. I want this, because I have to change the default installation directory and GUID. I am using the latest stable version of the cx_Freeze package and Python 3.7.9. When I run the setup() function inside setup.py it creates the first installer without any problem, but at the second iteration I get an error:

running install_exe
copying build\exe.win32-3.7\frozen_application_license.txt -> build\bdist.win32\msi
error: could not create 'build\bdist.win32\msi\frozen_application_license.txt': No such file or directory 

I already tried to remove the build dir after every iteration or modify argv after every iteration, but that won't work.

Here's a minimal example of the application to run and get an error. I just run python setup.py to create the installers:

setup.py

import sys
from cx_Freeze import setup, Executable

sys.argv.append("bdist_msi")

programs = {  # name, GUID pairs
    "hello": "{6ae7456f-2761-43a2-8a23-1a3dd284e947}",
    "world": "{494d5953-651d-41c5-a6ef-9156c96987a1}",
}

for program, GUID in programs.items():
    setup(
        name=f"Hello-{program}",
        version="1.0",
        executables=[Executable("hello.py")],
        options={
            "bdist_msi": {
                "initial_target_dir": f"C:\{program}",
                "upgrade_code": GUID,
            },
        },
    )

hello.py

from tkinter import *
from tkinter import ttk

root = Tk()
frm = ttk.Frame(root, padding=10)
frm.grid()
ttk.Label(frm, text="Hello World!").grid(column=0, row=0)
ttk.Button(frm, text="Quit", command=root.destroy).grid(column=1, row=0)
root.mainloop()

Solution

  • I would recommend to solve this by running one build step for each customer using a shell/command script and parametrize with environment variables:

    setup.py

    import os
    import sys
    from cx_Freeze import setup, Executable
    
    sys.argv.append("bdist_msi")
    
    program = os.environ['PROGRAM']
    GUID = os.environ['GUID']
    
    setup(
        name=f"Hello-{program}",
        version="1.0",
        executables=[Executable("hello.py")],
        options={
            "bdist_msi": {
                "initial_target_dir": f"C:\{program}",
                "upgrade_code": GUID,
            },
        },
    )
    

    build.sh / build.cmd

    # Customer 1
    PROGRAM = "hello"
    GUID = "6ae7456f-2761-43a2-8a23-1a3dd284e947"
    python setup.py
    
    # Maybe clean build output
    
    # Customer 2
    PROGRAM = "world"
    GUID = "494d5953-651d-41c5-a6ef-9156c96987a1"
    python setup.py
    

    With this structure you could split further and for example have one CI/CD pipeline for each customer.