Skip to content

Commit

Permalink
add --save-spec and --keep-tk flags to freeze32
Browse files Browse the repository at this point in the history
  • Loading branch information
jborbely committed Feb 17, 2025
1 parent 589c429 commit f35b185
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 16 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ a_new_file.txt

# Auto-generated during builds
/src/msl/loadlib/_version.py

# Might be generated when freezing the server
file_version_info.txt
server32.spec
5 changes: 3 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Version 1.0.0 (in development)

- :class:`~msl.loadlib.client64.Client64` now accepts ``None`` as
the value of `host`, which will mock the connection to the server
- support for Python 3.13
- support for Python 3.12
- type annotations (:PEP:`484` and :PEP:`561` using inline types)
- ``freeze32`` console script to create a new server
Expand All @@ -18,8 +19,8 @@ Version 1.0.0 (in development)
- convert to a :PEP:`420` implicit namespace package
- the `requires_pythonnet` and `requires_comtypes` arguments to
:func:`freeze_server32.main() <msl.loadlib.freeze_server32.main>`
were removed and the `imports`, `data` and `skip_32bit_check`
arguments were added
were removed and the `imports`, `data`, `skip_32bit_check`, `save_spec`
and `keep_tk` arguments were added
- constants (e.g., `IS_WINDOWS`) were moved to a `constants.py` file,
these constants are meant for internal use only

Expand Down
83 changes: 69 additions & 14 deletions src/msl/loadlib/freeze_server32.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os
import sys
from importlib import import_module
from shutil import copy
from subprocess import check_call
from tempfile import TemporaryDirectory
from typing import Iterable
Expand All @@ -30,7 +31,9 @@ def main(*,
dest: str | None = None,
imports: str | Iterable[str] | None = None,
data: str | Iterable[str] | None = None,
skip_32bit_check: bool = False) -> None:
skip_32bit_check: bool = False,
save_spec: bool = False,
keep_tk: bool = False) -> None:
"""Create a frozen server.
This function should be run using a 32-bit Python interpreter with
Expand All @@ -44,7 +47,8 @@ def main(*,
.. versionchanged:: 1.0
Removed the `requires_pythonnet` and `requires_comtypes` arguments.
Added the `imports`, `data` and `skip_32bit_check` arguments.
Added the `imports`, `data`, `skip_32bit_check`, `save_spec` and `keep_tk`
arguments.
.. _PyInstaller: https://www.pyinstaller.org/
Expand All @@ -67,6 +71,13 @@ def main(*,
the server. Before you create a 64-bit server, decide if
:ref:`msl-loadlib-mock-connection` is a better solution for your
application.
:param save_spec: By default, the `.spec` file that is created (when the
server is frozen) is deleted. Setting this value to :data:`True` will
save the `.spec` file, so that it may be modified and then passed as
the value to the `spec` parameter.
:param keep_tk: By default, the :mod:`tkinter` module is excluded from the
server. Setting this value to :data:`True` will bundle :mod:`tkinter`
with the server.
.. attention::
If a value for `spec` is specified, then `imports` nor `data` may be
Expand Down Expand Up @@ -151,7 +162,7 @@ def main(*,
f'Cannot freeze the server', file=sys.stderr)
return

cmd.extend(_get_standard_modules())
cmd.extend(_get_standard_modules(keep_tk))

if data:
major, *rest = pyinstaller_version.split('.')
Expand Down Expand Up @@ -189,10 +200,32 @@ def main(*,
if imports and ('pythonnet' in imports):
loadlib.utils.check_dot_net_config(server_path)

print(f'Server saved to {server_path}')
if save_spec:
print(f'The following files were saved to {dist_path}\n'
f' {constants.SERVER_FILENAME}')

if os.path.isfile(f'{server_path}.config'):
print(f' {os.path.basename(server_path)}.config')

def _get_standard_modules() -> list[str]:
spec_file = 'server32.spec'
copy(
os.path.join(work_path, f'{constants.SERVER_FILENAME}.spec'),
os.path.join(dist_path, spec_file)
)
print(f' {spec_file}')

if constants.IS_WINDOWS:
file_version_info = 'file_version_info.txt'
copy(
os.path.join(work_path, file_version_info),
dist_path
)
print(f' {file_version_info} (required by the {spec_file} file)')
else:
print(f'Server saved to {server_path}')


def _get_standard_modules(keep_tk: bool) -> list[str]:
"""
Returns a list of standard python modules to include and exclude in the
frozen application.
Expand Down Expand Up @@ -223,11 +256,12 @@ def _get_standard_modules() -> list[str]:
'idlelib',
'lib2to3',
'test',
'tkinter',
'_tkinter',
'turtle',
]

if not keep_tk:
ignore_list.extend(['tkinter', '_tkinter'])

# some modules are platform specific and got a
# RecursionError: maximum recursion depth exceeded
# when running this script with PyInstaller 3.3 installed
Expand Down Expand Up @@ -320,29 +354,35 @@ def _cli() -> None:
parser = argparse.ArgumentParser(
description='Create a frozen server for msl-loadlib.',
formatter_class=argparse.RawTextHelpFormatter,
add_help=False,
)
parser.add_argument(
'-h', '--help',
action='help',
default=argparse.SUPPRESS,
help='Show this help message and exit.'
)

parser.add_argument(
'-s', '--spec',
help='the path to a PyInstaller .spec file'
help='The path to a PyInstaller .spec file.'
)
parser.add_argument(
'-d', '--dest',
help='the destination directory to save the server to\n'
help='The destination directory to save the server to.\n'
'(Default is the current directory)'
)
parser.add_argument(
'-i', '--imports',
nargs='*',
help='the names of modules that must be importable on the server\n'
help='The names of modules that must be importable on the server.\n'
'Examples:\n'
' --imports msl.examples.loadlib\n'
' --imports mypackage numpy'
)
parser.add_argument(
'-D', '--data',
nargs='*',
help='additional data files to bundle with the server -- the\n'
help='Additional data files to bundle with the server -- the\n'
'format is "source:dest_dir", where "source" is the path\n'
'to a file (or a directory of files) to add and "dest_dir"\n'
'is an optional destination directory, relative to the\n'
Expand All @@ -354,15 +394,28 @@ def _cli() -> None:
' --data mydata/lib1.dll mydata/bin/lib2.dll:bin\n'
' --data mypackage/lib32.dll:mypackage'
)

parser.add_argument(
'--skip-32bit-check',
action='store_true',
help='in the rare situation that you want to create a frozen\n'
help='In the rare situation that you want to create a frozen\n'
'64-bit server, you can include this flag which skips the\n'
'requirement that a 32-bit version of Python must be used\n'
'to create the server.'
)
parser.add_argument(
'--save-spec',
action='store_true',
help='By default, the PyInstaller ".spec" file (that is created\n'
'when the server is frozen) is deleted. Including this\n'
'flag will save the ".spec" file, so that it may be modified\n'
'and then passed as the value to the "--spec" option.'
)
parser.add_argument(
'--keep-tk',
action='store_true',
help='By default, the tkinter module is excluded from the server.\n'
'Including this flag will bundle tkinter with the server.'
)

args = parser.parse_args(sys.argv[1:])

Expand All @@ -373,5 +426,7 @@ def _cli() -> None:
imports=args.imports,
data=args.data,
skip_32bit_check=args.skip_32bit_check,
save_spec=args.save_spec,
keep_tk=args.keep_tk,
)
)

0 comments on commit f35b185

Please sign in to comment.