-
-
Notifications
You must be signed in to change notification settings - Fork 31.3k
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
Add an option to zipapp to produce a Windows executable #72434
Comments
The zipapp module allows users to bundle their application as a single file "executable". On Windows, the file is given a ".pyz" extension which is associated with the Python launcher. However, this approach is not always equivalent to a native executable (see http://paul-moores-notes.readthedocs.io/en/latest/wrappers.html for more details). I suggest adding an option to zipapp that prepends a small executable to the zipapp that uses the Python C API to launch the application. A prototype implementation (zastub) is available at https://github.com/pfmoore/pylaunch. If this seems reasonable, I'll work up a full patch. |
Why not just change the extension to cmd and add the following line at the start?
|
(1) It starts an extra process (unless you're running the application from cmd.exe) and (2) in some cases, the system won't recognise a cmd file as an executable. For a simple example, t.cmd: @echo Hello from t example.py: from subprocess import run
run(["t")] If you run example.py you get "FileNotFoundError: [WinError 2] The system cannot find the file specified". |
Specifically, while CreateProcess does execute batch scripts via the %ComSpec% interpreter, the only extension it infers is ".exe". To run a ".cmd" or ".bat" file, you have to use the full name with the extension. |
I'm still unsure whether this would be a good idea. On the plus side, it provides (in conjunction with the "embedded" distribution) a really good way of producing a standalone application on Windows. On the minus side there are some limitations which may trip up naive users:
And the wrapper's basically only 4 lines of code:
so it's not exactly rocket science. (By the way, the arguments to Py_Main are "exactly as those which are passed to a C program’s main" - IIRC, for a C program there's always a NULL pointer at the end of argv, does that rule apply to Py_Main, too? The code doesn't seem to rely on it, so I guess I could save a slot in the array above). Maybe adding a section to the zipapp docs explaining how to make standalone applications would be a better way of handling this? That would have the advantage of being just as applicable to 3.6 (and 3.5, for that matter) at the cost of making users build their own wrapper (or find a published one). |
I think documentation is definitely the cheapest option. It's also the most flexible since if we find out there's a lot of usage of the guide then we can add explicit support. |
OK, here's a first draft documentation patch. As it's purely a documentation change, I guess it should go into the 3.5, 3.6 and trunk branches? For now it's against trunk (it's not like that file has changed recently anyway), and I'll sort out the merge dance once it looks OK. |
Only backport if you want; it's not a bug fix so technically it doesn't need to be backported. |
Hi Paul, Were you interested in moving forward with this doc change? |
Hi Cheryl, I'll try to get to it when I can, but it may not be for a few weeks, as I have other stuff on my plate at the moment. I've not done anything under the new github workflow, so I don't want to rush it and risk making mistakes. I'm happy if someone else wants to merge it in the meantime. |
Hi Paul, Thanks for your response. I've made the pull request from your patch, so it would great if you could review it. Thanks! |
The documentation helped a lot, so thanks for that! But it misses the final crucial step: copy /b zastub.exe+app.pyz app.exe The documentation talks about prepending the zastub.exe to the zip file but never mentions how, which is very confusing. |
While I can see your point, I'm a little skeptical that anyone who's able to write C source code and compile it can't work out how to combine two binary files... :-) |
@pfmoore many people are pretty good at following instructions as long as they are complete - lots of just-in-time learners out there 😄 I'm wondering about this little snippet:
I'm wondering how tricky it would be to embed that interpreter and if Python supports any official utilities to help with that? Perusing projects like https://github.com/pantsbuild/pex and linkedin/shiv#32 make it seem like it can be tricky cc: @csabella |
Actually, this reminded me that the instructions on how to build the executable are now out of date as distutils is no longer in the stdlib. And using the embedded interpreter is somewhat tricky. I’ve written various utilities over time to do this, but none really felt “production quality”. It’s much the same issue as the fact that code will mostly work if bundled in a zipapp - unless it won’t. There are a lot of details to get right and the stdlib documentation isn’t really the place to discuss them. On reflection, I think that the whole “Making a Windows Executable” section is probably better removed. I think that building fully standalone executables is a topic better suited for a 3rd party application. Maybe it is something shiv or pep would want to support? |
I should also have addressed this point. You are correct, but the problem here is less with the missing “how to append” step, and more with the fact that the rest of the instructions aren’t as complete as they look - they are suggestions that need to be applied with some care if they are to work. As such, they are probably more suitable as the subject of a blog post or article rather than being in the stdlib docs. |
I agree that fully standalone executables are best documented elsewhere, because they will almost certainly rely on templates and tooling that we do not provide. We could improve the documentation for working with the embeddable distro, and I've probably got 5-10 email threads worth of questions to help fill in the gaps. But even there there's enough variation in what people are doing to make it hard to cover all bases and also provide a workflow - a reference manual is hard to follow, but a tutorial largely won't be applicable. It's a really tough spot. (That said, if we had a standard mechanism for doing this that didn't work well with all existing libraries, I'm sure those libraries would adapt to work with it. We have a bit of scope to lead here, not merely follow all the existing conventions/assumptions.) |
Agreed. I think this is getting close to the question of whether (and how) the stdlib and core supports packaging. In this case, in the sense of "packaging up my Python code into a standalone tool/app/executable" rather than "packaging a Python library so that it can be imported". I think it's entirely reasonable to assume that the stdlib and core will provide mechanisms for implementing tools to build standalone executables, but not provide those high-level tools directly. So we have zipapp, but not shiv, for example. One part of this toolkit is zipapp, which covers bundling your Python code. A second part is the embeddable distribution, which covers shipping a dedicated interpreter with your app. A third part might be to enhance the I'm imagining something along the lines of:
Ship the resulting directory. The stdlib could even include a library that handled locating and copying the various "standard" items here (launcher, embeddable distribution) so that the user didn't have to manually locate and unpack them. Unfortunately, my C skills aren't really good enough to make a change that substantial to the launcher. I have written something similar (probably not production quality) in Rust, but I don't imagine we're likely to be able to ship code written in Rust with CPython (yet!). |
Most of the launcher is unnecessary if you've also got the embeddable distro somehow bundled. What we want here is a self-extracting app that can (safely!) generate the temp directories and then launch from in there (much like how many setup executables, including Python's, work). But the launcher should be pretty close to concatenating a .pyz and being able to run from that. I believe the old implementation has that in there (under one of the preprocessor selectors). Really it's just looking at its own file for the ZIP header, inserting itself into the command line and doing a normal search for already-installed Python. But I don't think this is really what people want. I think where it would make the most impact is integrated into a GUI framework (Electron, possibly?). Command-line users seem to be quite happy using other command-line tools to install things, but where the single-file executable really comes into its own is for users to download, double-click, and have just a normal app. I don't know of anyone seriously investing in this area though. |
Thanks for the comments. The PyOxidizer project by @indygreg looks like it's shaping up to be the most complete and polished third-party solution in this area. For my purposes, one thing that's nice about the builtin option is the trappings of being official which makes it an easier sell to the security and management folks. I just want to distribute Python apps with builtin interpreters in an enterprise environment and the fewer dependencies the better. Currently making do with shell scripts and some setup processes. I wouldn't mind cobbling together some additional code on my end to put the pieces together but sounds like it's more of a project. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: