Can't make a python 'runner' .exe: zipapp eg doesn't work

(Mark) #1

According to the zipapp module docs it is possible to create a python ‘runner’ .exe: make win exe
I copied the Python code into a .py file and got this error:

c1: fatal error C1083: Cannot open source file: 'zastub.c': No such file or directory
Traceback (most recent call last):
  File "C:\bin\py36\lib\distutils\", line 423, in compile
  File "C:\bin\py36\lib\distutils\", line 542, in spawn
    return super().spawn(cmd)
  File "C:\bin\py36\lib\distutils\", line 909, in spawn
    spawn(cmd, dry_run=self.dry_run)
  File "C:\bin\py36\lib\distutils\", line 38, in spawn
    _spawn_nt(cmd, search_path, dry_run=dry_run)
  File "C:\bin\py36\lib\distutils\", line 81, in _spawn_nt
    "command %r failed with exit status %d" % (cmd, rc))
distutils.errors.DistutilsExecError: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BI
N\\x86_amd64\\cl.exe' failed with exit status 2

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "", line 24, in <module>
  File "", line 16, in compile
    objs = cc.compile([str(src)])
  File "C:\bin\py36\lib\distutils\", line 425, in compile
    raise CompileError(msg)
distutils.errors.CompileError: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\x86
_amd64\\cl.exe' failed with exit status 2

Yet I do have cl.exe at this location:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\cl.exe

So I wonder if this is a doc error or that VS 14 installs are inconsistent?

PS I tried tagging with ‘help’ but there isn’t a tag of that name and it wouldn’t let me add one.

Creating a "python-dev" equivalent category
(Paul Moore) #2

You said you have cl.exe at C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\cl.exe.

Maybe it’s a VS inconsistency? That build script is only an example, maybe you’d be better just compiling the stub file by hand?

(Mark) #3

I can’t face compiling on Windows in C or C++; I’ll see if I can make a py runner in Rust instead.

(Paul Moore) #4

That sounds cool. If you get one working, I’d love to see it :slight_smile:

(Mark) #5

I tried using setuptools’ gui.exe but that wouldn’t work for me. The problem was the shebang line. I needed to use #!python/pythonw.exe because I’m bundling (a cut down) python in a python dir inside my app. But the shebang line seems to only accept an abs path; otherwise it reads from the env. I’m surprised there isn’t a solution for this.

I have two apps, one console, one GUI. In both cases they have a python subdir with the python I want them to use

For the console I’m just using a .bat file.

@echo off
"%~dp0\python\python.exe" "%~dp0\mycliapp.pyc" %*

This makes the app portable and ensures it uses its own python. Users run it with path\to\mycliapp.bat.

For the GUI I create a .exe file. In Windows task manager the app is shown as python not my exe’s name, but other than that it works fine.

All the rust app does is almost exactly the same as the above .bat, except it uses pythonw.exe and doesn’t create a console window when run. This means the user can double-click myguiapp.exe which is the rust app (<500K) which then just runs pythonw.exe with myguiapp.pyc. I built the rust runner as a 32-bit app since it doesn’t care whether it launches a 32- or 64-bit python. Another advantage of using a .exe runner is that I can set its icon etc., using rcedit.

Of course, it would be far better to write a rust app that loaded the python3.dll and then told it to run the main .pyc. There are rust crates that do this (e.g., pyo3 and cpython), but neither seemed to be quite what I wanted.

Now that I can make a ‘build’ dir that contains my apps and that is portable, I’ll try to slim down the python dir to just what I need. Then I can create packages.

(Steve Dower) #6

You might want to look at the embeddable zip package we have for download, which is already significantly reduced but (hopefully) without breaking anything that wasn’t deliberately left out (just distutils and venv, IIRC).

Compiling your own exe to call Py_Main is pretty straightforward, and if you still use the batch file to launch it then you don’t have to try and load anything yourself. The python._pth file (now renamed to match your exe name) will restrict sys.path and ignore env variables, and you can leave out as many or as few .pyd files from the package as you like.

Maybe we could have a tool that is basically a copy of python.vcxproj that works standalone from the CPython repo and a guide to updating the resources? It’s not hard, but it’s also not obvious. @pf_moore thoughts?

(Paul Moore) #7

And tk, I think.

Yes, the helper script I added to the docs (which @mark-summerfield tried to use) was probably being too clever for its own good. Personally, I find building C code a PITA, mainly because I work on machines where I only have the VS build tools installed, rather than Visual Studio, and I use powershell (so the bat files that activate VS are no use to me). That’s why I was pleased to find a pure Python solution, but it would probably have been better remaining as just my personal utility.

I’m not really sure what you mean here - as I say, I don’t use Visual Studio much. But I’m 100% in favour of anything that makes writing small command line wrappers round the Python interpreter easier.

Things I’ve struggled with in the past:

  1. Getting the build environment right (e.g., pointing at the Python headers).
  2. Linking to the Python DLL - the standard interpreter isn’t on PATH, and the embedded one requires some particularly messy manifest hacking if you want to put the embedded distribution in a subdirectory rather than dumping it in the same directory as your application exe.

But this is probably a separate discussion.

(Mark) #8

The reason I can’t use the embeddable zip is because it says it doesn’t have pip.
I need a minimal Python with pip so that I can then add the extra stuff I need.

I’ve written by own deployment script but when I tried the app on another computer it failed due to dependencies on win32api (despite the fact that the script copied all the win32* files from the python dir).

Maybe pywin32 installs some DLLs outside the python tree?

Anyway, I’ll have another go with PyInstaller & then try to get rid of my pywin32 dependencies. (I already know that py2exe and cx-freeze don’t work for me.)

(Paul Moore) #9

You should be able to use pip install --target to install whatever you need in a standalone directory (which your application can then add to sys.path). That’s the intended way to add packages to an embedded Python installation.

Or if you want to, you can modify the _pth file in the embedded distribution to add import site and then run to manually install pip into the embedded distribution - but note that if you do this, it’s unsupported, as the embedded distribution is not intended as a full Python development environment. But as a way to install what you need, for subsequent use as a runtime-only component, it’s a nice easy way to set things up.

(Steve Dower) #10

Maybe I’ll make a template repo with an Azure Pipelines build that can just do all of these steps. That also takes away the need for having Windows locally.

(Steve Dower) #11

Only semi-related, but what APIs are you using from pywin32? I’ve got half a plan together to build out some better (and more targeted) extensions, but I can always use more information about what people need to have access to.

(Mark) #12

I no longer use any pywin32 APIs directly — but I do use the module which depends on these APIs.

(Steve Dower) #13

The easier hacking is to move the .pyd and .zip files into a subdirectory and update the ._pth file. That gets you down to 2-4 files alongside your app, and if you’re referencing python3x.dll then you really want it there anyway.

(Paul Moore) #14

9 including python.exe and pythonw.exe, which are ones I really don’t want in the same directory as my app (PATH reasons) but I potentially do want available for running subprocesses. So still not as good as having a “python interpreter” subdirectory IMO.

Nevertheless, it’s a neat trick that I hadn’t thought of. Thanks.

(Tim Golden) #15

(I’m the author of the WMI module). Someone did produce a plug-compatible version which used ctypes or was in some other way not tied to the pywin32 modules. Just in case that helps you along. Can’t remember the name or anything I’m afraid but I assume it’ll be searchable.

(Mark) #16

I’ve used your WMI module for several years and found it easy to use and well documented. The wall I’ve hit is with its dependency, not the module itself.

I think you mean cWMI which I’ve now discovered thanks to your reply. I’ll give it a try — thanks.

(Tim Golden) #17

Thanks; glad it’s useful. (I’ve also corrected the previous post to make it clear that I meant it was not tied to pywin32, but you obviously worked that out anyway!)

(Mark) #18

I tried the cWMI module in a small script. Its .query() convenience method didn’t work, but the longer example did. However, I hit a problem when trying to use it with PySide: cannot change thread mode after it is set. I’ve added an issue to its github page.