Skip to content

Advertise zig and ziglang entry points for uv's sake #32

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

Merged
merged 6 commits into from
Jun 1, 2025

Conversation

torshepherd
Copy link
Contributor

@torshepherd torshepherd commented Jun 1, 2025

This PR advertises zig and ziglang executables in the entry_points.txt portion of the wheel. That means that tools like uv (https://docs.astral.sh/uv/guides/tools/#running-tools) can automagically pick them up:

uvx --refresh --from dist/ziglang-0.14.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl python-zig version

Installed 1 package in 667ms
0.14.1

When this is published to pypi, uv and pipx users will be able to invoke the zig compiler magically with

uvx --from ziglang python-zig

@torshepherd
Copy link
Contributor Author

If you run uv tool install on this wheel, you get this:

cat /home/tor/.local/bin/zig

#!/home/tor/.local/share/uv/tools/ziglang/bin/python
# -*- coding: utf-8 -*-
import sys
from ziglang.__main__ import main
if __name__ == "__main__":
    if sys.argv[0].endswith("-script.pyw"):
        sys.argv[0] = sys.argv[0][:-11]
    elif sys.argv[0].endswith(".exe"):
        sys.argv[0] = sys.argv[0][:-4]
    sys.exit(main())

This is more layers of course than I would like... but it works and has pretty low overhead

@whitequark
Copy link
Collaborator

whitequark commented Jun 1, 2025

from ziglang.__main__ import main

I believe that this would make __name__ == '__main__' always true, making your condition (and therefore the wrapping of it all into a function) entirely extraneous. Am I wrong?

@torshepherd
Copy link
Contributor Author

torshepherd commented Jun 1, 2025

Ok so I was really curious about this, so I added some printouts:

zig version
from __main__.py: __name__='ziglang.__main__'
from 'zig' entry_point: __name__='__main__'
0.14.1

So I think how name works is that it is set differently by the import mechanism?

@whitequark
Copy link
Collaborator

Actually, forget that. I now realize what your PR is doing, which I initially didn't because I forgot how entry_points work: it's installing a binary called zig (and ziglang) globally, which interferes with anything that would otherwise use a system ziglang.

This is intentionally not done in this package, see #7.

@torshepherd
Copy link
Contributor Author

torshepherd commented Jun 1, 2025

Ohhhh I see. Yeah that makes sense. FWIW uv doesn't do the bad thing mentioned there and overwrite "/usr/local", at least for me it puts it into $HOME/.local/bin symlinked to ~/.local/share/uv/tools/ziglang

One idea is to mark this as an "extra" feature, so that users have to uvx --from 'ziglang[cli]' zig in order to get it? This would mean that no current workflows get broken, just a new way for users to get access to the compiler. I believe uv even tells users when they leave off the feature, so discoverability would be fine. Would you be interested this? If not feel free to close this

@whitequark
Copy link
Collaborator

whitequark commented Jun 1, 2025

Would you be interested this? If not feel free to close this

I'm fairly sure that I've evaluated this and found that that even disabled "extra" features result in a binary being installed unconditionally, the binary just errors out when launched. I would suggest you re-check that; if it no longer installs a binary we can use it.

Alternately, since this is a repeatedly requested feature, we could simply generate and publish a new package, say ziglang-cli, which does nothing but installs a binary (with an exact version dependency on this package). Libraries using ziglang internally would never depend on this package, so this wouldn't break anyone's workflow.

@whitequark
Copy link
Collaborator

FWIW uv doesn't do the bad thing mentioned there and overwrite "/usr/local", at least for me it puts it into $HOME/.local/bin symlinked to ~/.local/share/uv/tools/ziglang

To be clear, this is still a problem: if you install something that depends on ziglang transitively, it should not install a desktop-wide (which is functionally identical to system-wide for most developers) binary overriding any existing user choice. The original goal for this package is to enable using Zig as a 'pure function' that generates code, so side effects like installing desktop-wide binaries are very undesirable.

@torshepherd
Copy link
Contributor Author

Yep, yeah I get that for sure. I think tbh ziglang-cli is a better option. This isn't really the use case for features.

Then maybe the ziglang PyPI page could link to either the ziglang-cli or simply the direct downloads page on https://ziglang.org/?

@whitequark
Copy link
Collaborator

I would expand the "Usage" section, something like this at the end:

To install a CLI binary named `zig` via PyPI, use the [ziglang-cli](https://pypi.org/packages/ziglang-cli) package instead.

and the ziglang-cli README should likewise direct library authors to ziglang.

@torshepherd
Copy link
Contributor Author

Oh wait, you know what, wouldn't this be trivially fixed by just naming the contributed script zig-wrapper-python or something? Name disambiguation, no other ziglang-cli package necessary. Then if any user wants to do the naughty thing of having zig refer to this wrapper, they can symlink it manually

@whitequark
Copy link
Collaborator

Oh wait, you know what, wouldn't this be trivially fixed by just naming the contributed script zig-wrapper-python or something? Name disambiguation, no other ziglang-cli package necessary.

It would; people just usually want an entry point that overlaps the system one.

Let's call it python-zig (I see python-argcomplete on my system so I'm going to optimistically assume that there is a convention and it is that). Now, you will need a tidier entrypoint. I suggest just adding def main(): """Dummy function for an entrypoint. Zig is executed as a side effect of the import.""" and be done with it, since our main function that calls os.execve will never return and this isn't something you want to ever import and call externally.

@torshepherd
Copy link
Contributor Author

In fact, wouldn't the original exported tool name "ziglang" also satisfy this? ziglang doesn't conflict with anything, though it doesn't make it clear that it's a python wrapper script in the same way zig-wrapper-python does

@whitequark
Copy link
Collaborator

In fact, wouldn't the original exported tool name "ziglang" also satisfy this? ziglang doesn't conflict with anything, though it doesn't make it clear that it's a python wrapper script in the same way zig-wrapper-python does

This is true but I would find it extremely confusing if I found two executables called zig and ziglang on my system that do more or less the same thing; and if in a PR someone called ziglang I'd assume it was a typo.

@torshepherd
Copy link
Contributor Author

Let's call it python-zig

done

I suggest just adding def main(): """Dummy function for an entrypoint. Zig is executed as a side effect of the import.""" and be done with it

named it dummy() but otherwise done

:)

@torshepherd
Copy link
Contributor Author

small note: if we instead used the previous main() fn approach, I could add a little printout at the top that says "you are calling the python wrapper for zig" but otherwise I like this outcome! Also updated the readme

Co-authored-by: Catherine <whitequark@whitequark.org>
Copy link
Collaborator

@whitequark whitequark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@whitequark whitequark merged commit f35198f into ziglang:main Jun 1, 2025
@torshepherd
Copy link
Contributor Author

🫡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants