Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do you package/distribute an enaml application? #425

Open
nicksspirit opened this issue Sep 12, 2020 · 9 comments
Open

How do you package/distribute an enaml application? #425

nicksspirit opened this issue Sep 12, 2020 · 9 comments

Comments

@nicksspirit
Copy link

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.

@MatthieuDartiailh
Copy link
Member

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

@nicksspirit
Copy link
Author

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.

@MatthieuDartiailh
Copy link
Member

Could you share the kind of error you get ?

@MatthieuDartiailh
Copy link
Member

Looking briefly through PyInstaller docs, I feel like you need to help PyInstaller realize it needs to package the enaml files (as data files).

@nicksspirit
Copy link
Author

But data files are not imported into python via the import system. They are usually accessed through some IO method like open()

@MatthieuDartiailh
Copy link
Member

MatthieuDartiailh commented Sep 14, 2020

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.

@MatthieuDartiailh
Copy link
Member

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.

@MatthieuDartiailh
Copy link
Member

MatthieuDartiailh commented Sep 14, 2020

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)?

@bburan
Copy link
Contributor

bburan commented Sep 7, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants