Skip to content

Commit

Permalink
gh-106111: Remove zipapp documentation on creating a Windows executab…
Browse files Browse the repository at this point in the history
…le (#106112)

Remove zipapp documentation on creating a Windows executable
  • Loading branch information
pfmoore committed Jun 26, 2023
1 parent 1a2bc94 commit 5d4dbf0
Showing 1 changed file with 9 additions and 106 deletions.
115 changes: 9 additions & 106 deletions Doc/library/zipapp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,115 +303,18 @@ the Python interpreter registers the ``.pyz`` and ``.pyzw`` file extensions
when installed.


Making a Windows executable
~~~~~~~~~~~~~~~~~~~~~~~~~~~

On Windows, registration of the ``.pyz`` extension is optional, and
furthermore, there are certain places that don't recognise registered
extensions "transparently" (the simplest example is that
``subprocess.run(['myapp'])`` won't find your application - you need to
explicitly specify the extension).

On Windows, therefore, it is often preferable to create an executable from the
zipapp. This is relatively easy, although it does require a C compiler. The
basic approach relies on the fact that zipfiles can have arbitrary data
prepended, and Windows exe files can have arbitrary data appended. So by
creating a suitable launcher and tacking the ``.pyz`` file onto the end of it,
you end up with a single-file executable that runs your application.

A suitable launcher can be as simple as the following::

#define Py_LIMITED_API 1
#include "Python.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef WINDOWS
int WINAPI wWinMain(
HINSTANCE hInstance, /* handle to current instance */
HINSTANCE hPrevInstance, /* handle to previous instance */
LPWSTR lpCmdLine, /* pointer to command line */
int nCmdShow /* show state of window */
)
#else
int wmain()
#endif
{
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
myargv[0] = __wargv[0];
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
return Py_Main(__argc+1, myargv);
}

If you define the ``WINDOWS`` preprocessor symbol, this will generate a
GUI executable, and without it, a console executable.

To compile the executable, you can either just use the standard MSVC
command line tools, or you can take advantage of the fact that distutils
knows how to compile Python source::

>>> from distutils.ccompiler import new_compiler
>>> import distutils.sysconfig
>>> import sys
>>> import os
>>> from pathlib import Path

>>> def compile(src):
>>> src = Path(src)
>>> cc = new_compiler()
>>> exe = src.stem
>>> cc.add_include_dir(distutils.sysconfig.get_python_inc())
>>> cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
>>> # First the CLI executable
>>> objs = cc.compile([str(src)])
>>> cc.link_executable(objs, exe)
>>> # Now the GUI executable
>>> cc.define_macro('WINDOWS')
>>> objs = cc.compile([str(src)])
>>> cc.link_executable(objs, exe + 'w')

>>> if __name__ == "__main__":
>>> compile("zastub.c")

The resulting launcher uses the "Limited ABI", so it will run unchanged with
any version of Python 3.x. All it needs is for Python (``python3.dll``) to be
on the user's ``PATH``.

For a fully standalone distribution, you can distribute the launcher with your
application appended, bundled with the Python "embedded" distribution. This
will run on any PC with the appropriate architecture (32 bit or 64 bit).


Caveats
~~~~~~~

There are some limitations to the process of bundling your application into
a single file. In most, if not all, cases they can be addressed without
needing major changes to your application.

1. If your application depends on a package that includes a C extension, that
package cannot be run from a zip file (this is an OS limitation, as executable
code must be present in the filesystem for the OS loader to load it). In this
case, you can exclude that dependency from the zipfile, and either require
your users to have it installed, or ship it alongside your zipfile and add code
to your ``__main__.py`` to include the directory containing the unzipped
module in ``sys.path``. In this case, you will need to make sure to ship
appropriate binaries for your target architecture(s) (and potentially pick the
correct version to add to ``sys.path`` at runtime, based on the user's machine).

2. If you are shipping a Windows executable as described above, you either need to
ensure that your users have ``python3.dll`` on their PATH (which is not the
default behaviour of the installer) or you should bundle your application with
the embedded distribution.

3. The suggested launcher above uses the Python embedding API. This means that in
your application, ``sys.executable`` will be your application, and *not* a
conventional Python interpreter. Your code and its dependencies need to be
prepared for this possibility. For example, if your application uses the
:mod:`multiprocessing` module, it will need to call
:func:`multiprocessing.set_executable` to let the module know where to find the
standard Python interpreter.
If your application depends on a package that includes a C extension, that
package cannot be run from a zip file (this is an OS limitation, as executable
code must be present in the filesystem for the OS loader to load it). In this
case, you can exclude that dependency from the zipfile, and either require
your users to have it installed, or ship it alongside your zipfile and add code
to your ``__main__.py`` to include the directory containing the unzipped
module in ``sys.path``. In this case, you will need to make sure to ship
appropriate binaries for your target architecture(s) (and potentially pick the
correct version to add to ``sys.path`` at runtime, based on the user's machine).


The Python Zip Application Archive Format
Expand Down

0 comments on commit 5d4dbf0

Please sign in to comment.