Fehler mit Python MIP/CBC nach erstellen einer exe

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Gerryman
User
Beiträge: 5
Registriert: Sonntag 19. März 2023, 18:43

Hallo Zusammen,

ich arbeite seit einiger Zeit an einem Programm, dass anhand einer Mathematischen Optimierung Ergebnisse liefern soll.
Für das ganze Projekt nutze ich
- Visual Studio Code
- Python 3.11.2 64-bit
- Windows 10, Core i7-12850HX

Ich habe mir mit tkinter eine umfangreiche UI erstellt, die bestens funktioniert.
Wenn ich meinen Code in VSC ausführe funktioniert auch alles. Selbst die Mathematische Optimierung läuft sauber durch und liefert mir die gewünschten Ergebnisse.

Mein nächster Schritt war nun, eine exe zu erstelle, damit meine Arbeitskollegen, das Programm auch nutzen können, ohne VSC, Python usw. zu benötigen.
Dafür habe ich pyinstaller genutzt. Nach anfänglichen Schwierigkeiten, habe ich es auch hinbekommen und mein Programm ist zu 99% lauffähig. Ich kann die UI in allen Bereichen nutzen, habe Zugriff auf meine SqliteDB usw.

Nur die Mathematische Optimierung funktioniert nicht!
Wenn ich mein Programm in der Konsole starte, bekomme ich sobald ich die Berechnung starte, folgende Meldung:

Code: Alles auswählen

Exception in thread Thread-2:
Traceback (most recent call last):
  File "threading.py", line 1038, in _bootstrap_inner
  File "optimization.py", line 1078, in run
  File "mip\model.py", line 87, in __init__
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "PyInstaller\loader\pyimod02_importers.py", line 391, in exec_module
  File "mip\cbc.py", line 603, in <module>
NameError: name 'cbclib' is not defined
Wenn ich den Fehler bis zur Zeile 603 verfolge, lande ich erst hier:

Code: Alles auswählen

Osi_getNumCols = cbclib.Osi_getNumCols
und dann hier:

Code: Alles auswählen

            if os_is_64_bit:
                libfile = os.path.join(pathlib, "cbc-c-darwin-x86-64.dylib")
        if not libfile:
            raise NotImplementedError("You operating system/platform is not supported")
    old_dir = os.getcwd()
    os.chdir(pathlib)
    cbclib = ffi.dlopen(libfile)
    
und dann hier:

Code: Alles auswählen

try:
    pathmip = dirname(mip.__file__)
    pathlib = os.path.join(pathmip, "libraries")
    libfile = ""
    # if user wants to force the loading of an specific CBC library
    # (for debugging purposes, for example)
    if "PMIP_CBC_LIBRARY" in os.environ:
        libfile = os.environ["PMIP_CBC_LIBRARY"]
        pathlib = dirname(libfile)

        if platform.lower().startswith("win"):
            if pathlib not in os.environ["PATH"]:
                os.environ["PATH"] += ";" + pathlib
    else:
        if "linux" in platform.lower():
            if os_is_64_bit:
                pathlibe = pathlib
                libfile = os.path.join(pathlib, "cbc-c-linux-x86-64.so")
                if not exists(libfile):
                    pathlibe = pathlib
                    libfile = os.path.join(pathlib, "cbc-c-linux-x86-64.so")
                pathlib = pathlibe
            else:
                raise NotImplementedError("Linux 32 bits platform not supported.")
        elif platform.lower().startswith("win"):
            if os_is_64_bit:
                pathlibe = os.path.join(pathlib, "win64")
                libfile = os.path.join(pathlibe, "cbc-c-windows-x86-64.dll")
                if exists(libfile):
                    if pathlibe not in os.environ["PATH"]:
                        os.environ["PATH"] = pathlibe + ";" + os.environ["PATH"]
                else:
                    pathlibe = pathlib
                    libfile = os.path.join(pathlibe, "cbc-c-windows-x86-64.dll")
                    if pathlibe not in os.environ["PATH"]:
                        os.environ["PATH"] = pathlibe + ";" + os.environ["PATH"]
                pathlib = pathlibe

            else:
                raise NotImplementedError("Win32 platform not supported.")
        elif platform.lower().startswith("darwin") or platform.lower().startswith(
            "macos"
        ):
            if os_is_64_bit:
                libfile = os.path.join(pathlib, "cbc-c-darwin-x86-64.dylib")
        if not libfile:
            raise NotImplementedError("You operating system/platform is not supported")
Die Dateien sind zwar auf meinem Rechner, werden beim erstellen aber wohl nicht mit integriert.
C:\Users\Dominik\AppData\Local\Programs\Python\Python311\Lib\site-packages\mip\libraries\win64\cbc-c-windows-x86-64.dll
C:\Users\q180341\AppData\Local\Programs\Python\Python311\Lib\site-packages\mip\libraries\cbc-c-darwin-x86-64.dylib
Ich bin jetzt seit Wochen auf der Suche nach einer Lösung, komme aber zu keiner Lösung.
Diverses googlen, Foren durchsuchen und ChatGPT befragen habt mich nicht weiter gebracht. Das einzige was ich bisher rausgefunden habe ist, dass ich nicht der einzige bin. Diesen Fehler gibt es wohl häufiger, aber eine umsetzbare Lösung ist noch nicht dabei gewesen. Bei einigen scheint es auch ohne Probleme zu funktionieren....

Vielleicht hat hier jemand Erfahrung, oder hat eine Idee woran es noch liegen kann. Evtl. ist die Lösung ganz einfach und ich mache einfach irgendetwas falsch...
Ich bin für jede Hilfe dankbar.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der Code ist doch recht simpel: libfile zeigt auf die Stelle, wo er die DLL sucht. Da liegt die nicht.

Pyinstaller muss die eben mit installieren, dazu hat der Argumente. Und ggf. musst du vor dem import PIMP_CPB_LIBRARY in os.environ auf den Pfad umbiegen, auf den pyinstaller den platziert hat, wenn es zu schwer ist, das an die erwartete Stelle relativ zu pathlib zu packen.
Gerryman
User
Beiträge: 5
Registriert: Sonntag 19. März 2023, 18:43

Wunderschönen guten Morgen,

danke erst mal für deine Antwort.
Ich muss zugeben, dass ich sie drei mal lesen musste, bevor ich sie verstanden habe.

Aber die Lösung war tatsächlich so simpel. ich habe in das CBC Modul ein paar print Anweisungen eingefügt, damit ich mal sehe, wo das Programm nach den Dateien sucht und hab sie dann an die entsprechende Stelle kopiert.

Diese beiden Dateien wurden beim erstellen nicht übernommen
cbc-c-darwin-x86-64.dylib
win64/cbc-c-windows-x86-64.dll
Nächster Fehler war dann, dass das komplette "wakepy" Modul gefehlt hat, das auch noch kopiert, aber dann lief alles wie gewollt!

Also nochmal danke für deine Hilfe, war ein wichtiger boost!

Jetzt hab ich aber noch ein paar Verständnis Fragen:
- Warum werden die Dateien beim erstellen nicht mit herangezogen? Alles andere war ja mit dabei... (mir ist bewusst, dass ich viel noch nicht weiß und leider nicht die Zeit habe mich in alle Themen tief einzuarbeiten)
Pyinstaller muss die eben mit installieren, dazu hat der Argumente.
Mein versuch sah gestern schon so aus, allerdings ohne Ergebnis. Ist das das, was du meintest, oder war ich da völlig auf dem Holzweg?

Code: Alles auswählen

pyinstaller --add-binary "C:\Users\Dominik\AppData\Local\Programs\Python\Python311\Lib\site-packages\mip\libraries\cbc-c-darwin-x86-64.dylib;cbc" --add-binary "C:\Users\Dominik\AppData\Local\Programs\Python\Python311\Lib\site-packages\mip\libraries\win64\cbc-c-windows-x86-64.dll;cbc" fwsim23.py
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich haette statt --add-binary eher --add-data benutzt, aber prinzipiell ist das schon das richtige. Das Darwin-Ding ist aber quatsch, warum installierst du das? Einen Universal-Installer fuer alle Plattformen gibt es nicht.

Dann musst du inspizieren, wo das Ergebnis wirklich landet, und wie du aus deinem Code darauf referenzierst. ACHTUNG: der User kann den Ort der Installation bestimmen! Da darf also nix hard-kodiert werden. Pyinstaller hat aber Moeglichkeiten, das man zur Laufzeit das Verzeichnis, in dem das Projekt installiert ist, rausfinden kann. Ist alles dokumentiert. Und dann eben ggf. die Umgebungsvariable setzen, wenn der Code das sonst nicht findet.

Warum das nicht automatisch klappt? Schau dir mal deine ~20 oder so Zeilen da oben an, mit denen dein Paket diese DLL zusammengesammelt. Du hast davor kapituliert ohne meine Erklaerungen, und pyinstaller ist ja nun nur ein dummes Programm. Das kann also nicht rausfinden, welche Dateien wohin gehoeren. Darum muss man fuer solche Abhaengigkeiten spec-Files beitragen, damit es das kann. Vielleicht kannst du sowas fuer deine Bibliothek auch beitragen (keine Ahnung, ob es da einen Prozess fuer gibt).
Antworten