Skip to content

Commit 64900bc

Browse files
committed
supporting code and docs for freezing flexx apps!
1 parent 5643373 commit 64900bc

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

docs/about.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ About
44

55
.. toctree::
66
:maxdepth: 2
7-
7+
88
releasenotes
99
contributing
10+
freeze
1011
overview
1112
motivation
1213
eventsystembackground

docs/freeze.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Freezing Flexx apps
2+
-------------------
3+
4+
Flexx needs special care when freezing, because it needs access to the Python
5+
source code in order to compile it to JavaScript.
6+
7+
First, create a script that represents your application entry point. It
8+
is important that this script does not define any new Flexx widgets. It
9+
should look something like this:
10+
11+
.. code-block:: py
12+
13+
# Install hook so we we can import modules from source when frozen.
14+
from flexx.util import freeze
15+
freeze.install()
16+
17+
# Run your app as usual
18+
from flexx import flx
19+
from my_module import MyWidget
20+
app = flx.App(MyWidget)
21+
app.launch("firefox-app")
22+
flx.run()
23+
24+
25+
Next, use ``PyInstaller`` as usual to create an app directory. Consider
26+
using ``--exclude-module numpy`` (PyInstaller thinks that Flexx needs Numpy, but this not the case).
27+
28+
Next, copy the source code of the modules that define Flexx widgets. If you
29+
run PyInstaller from a script (using ``PyInstaller.__main__.run()``) then you
30+
can combine it.
31+
32+
.. code-block:: py
33+
34+
from flexx.util import freeze
35+
36+
freeze.copy_module("flexx", app_dir) # always
37+
freeze.copy_module("flexxamples", app_dir) # used something from here?
38+
freeze.copy_module("my_module", app_dir)
39+
40+
That should be it!

flexx/util/freeze.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
""" Utilities for freezing apps that use Flexx.
2+
"""
3+
4+
import os
5+
import sys
6+
import shutil
7+
import importlib
8+
9+
10+
def copydir(path1, path2):
11+
# like shutil.copytree, but ...
12+
# * ignores __pycache__directories
13+
# * ignores hg, svn and git directories
14+
# Ensure destination directory does exist
15+
if not os.path.isdir(path2):
16+
os.makedirs(path2)
17+
# Itereate over elements
18+
count = 0
19+
for sub in os.listdir(path1):
20+
fullsub1 = os.path.join(path1, sub)
21+
fullsub2 = os.path.join(path2, sub)
22+
if sub in ['__pycache__', '.hg', '.svn', '.git']:
23+
continue
24+
elif sub.endswith('.pyc') and os.path.isfile(fullsub1[:-1]):
25+
continue
26+
elif os.path.isdir(fullsub1):
27+
count += copydir(fullsub1, fullsub2)
28+
elif os.path.isfile(fullsub1):
29+
shutil.copy(fullsub1, fullsub2)
30+
count += 1
31+
# Return number of copies files
32+
return count
33+
34+
35+
def copy_module(module, app_dir):
36+
""" Copy the source of the given module to the given application directory.
37+
"""
38+
if isinstance(module, str):
39+
module = importlib.import_module(module)
40+
filename = module.__file__
41+
if filename.endswith("__init__.py"):
42+
copydir(os.path.dirname(filename),
43+
os.path.join(app_dir, "source", module.__name__))
44+
elif filename.endswith(".py"):
45+
shutil.copy(filename,
46+
os.path.join(app_dir, "source", module.__name__ + ".py"))
47+
48+
49+
class SourceImporter:
50+
51+
def __init__(self, dir):
52+
self.module_names = set()
53+
for name in os.listdir(dir):
54+
if name.endswith(".py"):
55+
self.module_names.add(name[:-3])
56+
elif "." not in name:
57+
self.module_names.add(name)
58+
59+
def find_spec(self, fullname, path, target=None):
60+
if fullname.split(".")[0] in self.module_names:
61+
for x in sys.meta_path:
62+
if getattr(x, "__name__", "") == "PathFinder":
63+
return x.find_spec(fullname, path, target)
64+
return None
65+
66+
67+
def install():
68+
""" Install the imported to allow importing from source.
69+
"""
70+
if getattr(sys, 'frozen', False):
71+
print(sys.meta_path)
72+
source_dir= os.path.join(sys._MEIPASS, 'source')
73+
sys.path.insert(0, source_dir)
74+
sys.meta_path.insert(0, SourceImporter(source_dir))
75+
for key in [x for x in sys.modules if x.startswith("flexx")]:
76+
sys.modules.pop(key)

0 commit comments

Comments
 (0)