enaml
enaml copied to clipboard
How do you package/distribute an enaml application?
I built a simple app using enaml called date range chunker. I want to share it with others who won't have python installed in their system.
What is the recommended approach to packaging/distributing my application to people using windows. I tried to make use of pyinstaller but every time I try to start the app it fails. It is not able to execute the dynamic import of the .enaml
files via the enaml.imports()
context manager.
This is not something I ever investigated myself. The topic was discussed a couple of times on the mailing list though. In particular one person provided a build script for pyinstaller listing a number of hidden imports he needed to add to get things working. It may help, note however that it was quite some time ago so some imports may not be correct anymore due to some internal reorganization.
https://github.com/jminardi/syncnet/blob/master/build_app.sh
So I tried to follow the code in the build_app.sh. I noticed that the script was targeted for MacOS but I am running windows.
Either way I tried to add the lines regarding hidden import
...
PACKAGE_NAME = "enaml_demo"
EXECUTABLE_NAME = "person"
ENTRYPOINT = BASE_DIR / PACKAGE_NAME / "person.py"
ASSETS = BASE_DIR / PACKAGE_NAME / "assets"
def resolve(s: Path) -> str:
return str(s.resolve())
def build():
pyinstaller = import_module("PyInstaller.__main__")
pyinstaller.run(
[
"-y",
f"--name={EXECUTABLE_NAME}",
"--onedir",
'--console',
"--clean",
'--hidden-import="enaml.core.parse_tab.lextab"',
'--hidden-import="enaml.core.compiler_helpers"',
'--hidden-import="enaml.core.compiler_nodes"',
'--hidden-import="enaml.core.enamldef_meta"',
'--hidden-import="enaml.core.template"',
'--hidden-import="enaml.widgets.api"',
'--hidden-import="enaml.widgets.form"',
'--hidden-import="enaml.layout.api"',
resolve(ENTRYPOINT),
]
)
...
But still no success, once the app is built it won't run because of the import of the enaml file.
Could you share the kind of error you get ?
Looking briefly through PyInstaller docs, I feel like you need to help PyInstaller realize it needs to package the enaml files (as data files).
But data files are not imported into python via the import system. They are usually accessed through some IO method like open()
Yeah but .enaml files cannot be identified by pyinstaller as being needed since for an import it is only gonna look for a matching .py which does not exist. In that respect .enaml files are data files. We hook into the Python import system to load them actually.
Using the following I was able to package the employee example:
pyinstaller employee/employee.py --hiddenimport "enaml.core.compiler_helpers" --hiddenimport "atom.api" --add-data employee/employee_view.enaml:. --hiddenimport "enaml.layout.api" --hiddenimport "enaml.core.enamldef_meta" --hiddenimport "enaml.widgets.api" --hiddenimport "phone_validator"
It is ugly and we could do much better, but it is a start.
To do a better job, we will need a fairly complex hook to inspect all the .enaml files of the packaged application to find missing imports, to package the lextab and parsetab for the relevant Python version for enaml, to package all the relevant enaml files and if they exists the matching enamlc files (the hook may want to force the compilation of enaml files to be able to bundle the enamlc file to avoid the parsing/compilation cost each time the app is run). Based on PyInstaller recommendation enaml could distribute its own hook. I would be happy to do so but may not have much time to work on the hook itself. Is it something you would have time/be interested in working on @OdinTech3 (once we get your use case working)?
I was able to get this working by creating a special hook for Enaml. It's a bit crude, but it does make sure the *.enaml files get included as data. Future revisions should probably include the *.enamlc files instead (not sure if the *.enaml files are needed as well). However, I won't have further time to explore this for a bit. Here's the hook code:
from PyInstaller.utils.hooks import collect_data_files
datas = collect_data_files('enaml', True)
Really simple and it works with PyInstaller. See the full repository at https://github.com/NCRAR/ncrar-abr-installer for the process to create a stand-alone installer for a program. For example, see how to include the hooks in your spec file for PyInstaller.