Skip to content
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 dynamically linked musl distributions #541

Merged
merged 34 commits into from
Mar 11, 2025
Merged

Add dynamically linked musl distributions #541

merged 34 commits into from
Mar 11, 2025

Conversation

zanieb
Copy link
Member

@zanieb zanieb commented Feb 26, 2025

As a consequence of #540 I was playing with these concepts and decided to explore it.

This includes #546 (could be merged separately or together), which separates "static" builds from the "musl" triple specifically in favor of a dedicated build option.

The main implementation downside here is that $ORIGIN doesn't work with DT_NEEDED so we need to use RUNPATH instead, which can cause the wrong library to be loaded if LD_LIBRARY_PATH is set. Given the current builds aren't usable at all, I think this is a fine trade-off. We can explore alternatives in the future, like statically linking just libpython.

Another caveat here: for consistency with the glibc builds, we're changing the "default" musl build to be dynamically linked. This is a breaking change in the release artifacts. The statically linked musl build will include a +static suffix. We could do something for backwards compatibility here, but I think this probably makes sense in the long term. My primary concern is that consumers that combine releases (such as uv) would need to encode this change (e.g., toggle the expectation based on the python-build-standalone version tag).

It's challenging to test changes to the release artifact handling. Regardless of approach, this will need a follow-up to adjust that accordingly.

@zanieb zanieb added the platform:linux Specific to the Linux platform label Feb 26, 2025
@zanieb
Copy link
Member Author

zanieb commented Feb 26, 2025

I got the x86_64-unknown-linux-musl-noopt build passing on my machine and gave it a quick test in an alpine container and it looks like I didn't package it right.

/test # ldd ./python/install/bin/python
	/lib/ld-musl-x86_64.so.1 (0x7ebb73af0000)
Error loading shared library $ORIGIN/../lib/libpython3.13.so.1.0: No such file or directory (needed by ./python/install/bin/python)
	libc.so => /lib/ld-musl-x86_64.so.1 (0x7ebb73af0000)
Error relocating ./python/install/bin/python: Py_BytesMain: symbol not found

I presumed we just didn't package the .so, but it is there

/ # ls /test/python/install/lib/
Tix8.4.3              libpython3.13.so      pkgconfig             tcl8.6
itcl4.2.4             libpython3.13.so.1.0  python3.13            thread2.8.9
libpython3.13.a       libpython3.so         tcl8                  tk8.6

So I futzed with patchelf

/ # patchelf --set-rpath '/test/python/install/lib' /test/python/install/bin/python
/ # ldd /test/python/install/bin/python
	/lib/ld-musl-x86_64.so.1 (0x79dc36f38000)
Error loading shared library $ORIGIN/../lib/libpython3.13.so.1.0: No such file or directory (needed by /test/python/install/bin/python)
	libc.so => /lib/ld-musl-x86_64.so.1 (0x79dc36f38000)
Error relocating /test/python/install/bin/python: Py_BytesMain: symbol not found

Huh

/ # readelf -d /test/python/install/bin/python

Dynamic section at offset 0x388 contains 21 entries:
  Tag        Type                         Name/Value
 0x000000000000001d (RUNPATH)            Library runpath: [/test/python/install/lib]
 0x0000000000000001 (NEEDED)             Shared library: [$ORIGIN/../lib/libpython3.13.so.1.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]
 0x000000000000000c (INIT)               0x401000
 0x000000000000000d (FINI)               0x401148
 0x0000000000000019 (INIT_ARRAY)         0x403e48
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x403e50
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x0000000000000004 (HASH)               0x3ff468
 0x000000006ffffef5 (GNU_HASH)           0x3ff428
 0x0000000000000005 (STRTAB)             0x3fd2e0
 0x0000000000000006 (SYMTAB)             0x3ff338
 0x000000000000000a (STRSZ)              167 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x403fe8
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400470
 0x0000000000000000 (NULL)               0x0

I guess I'll try harder?

/ # patchelf --replace-needed '$ORIGIN/../lib/libpython3.13.so.1.0' '/test/python/install/lib/libpython3.13.so.1.0' /test/pyt
hon/install/bin/python
/ # /test/python/install/bin/python --version
Python 3.13.2

Seems like $ORIGIN probably isn't supported? Need to look into that.

@zanieb
Copy link
Member Author

zanieb commented Feb 26, 2025

ORIGIN is supported for RPATH, but seemingly not for NEEDED (ref https://git.musl-libc.org/cgit/musl/tree/ldso/dynlink.c#n897) — switching to that for now.

This is what it is available on ubuntu-22.04
@zanieb
Copy link
Member Author

zanieb commented Feb 26, 2025

Hm okay something is wrong with Python 3.11 — peachy otherwise!

edit: It's loading the libpython from the system, which is 3.11.

@zanieb
Copy link
Member Author

zanieb commented Feb 26, 2025

This is loosely ready for review. I have some minor clean-ups and documentation to do.

@zanieb zanieb marked this pull request as ready for review February 26, 2025 20:45
@zanieb zanieb requested a review from geofft February 26, 2025 20:55
Copy link
Collaborator

@indygreg indygreg left a comment

Choose a reason for hiding this comment

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

I strongly prefer retaining the ability to produce a statically linked musl distribution. There are some users of PyOxidizer who really like single file binaries. And you only get that if you statically link musl.

I would prefer standing up a new target variant with dynamic linked musl which is ABI compatible with the musl Python platform/target.

@zanieb
Copy link
Member Author

zanieb commented Feb 27, 2025

I can look into that. Will require #542.

zanieb added 3 commits March 5, 2025 13:37
# Conflicts:
#	cpython-unix/build-cpython.sh
#	cpython-unix/build.py
#	pythonbuild/cpython.py
Comment on lines -123 to -124
# TODO should the musl target be normalized?
.replace("-unknown-linux-musl", "-unknown-linux-gnu")
Copy link
Member Author

Choose a reason for hiding this comment

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

See xproto and xextproto build changes

Copy link
Member Author

Choose a reason for hiding this comment

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

The answer to this todo seems to be: no it should not generally be normalized like this because it causes problems. But then there are a couple specific builds where it does need to be normalized because there isn't support for -musl targets.

See also, the tcl build change.

"url": "https://musl.libc.org/releases/musl-1.2.2.tar.gz",
"size": 1055220,
"sha256": "9b969322012d796dc23dda27a35866034fa67d8fb67e0e2c45c913c3d43219dd",
"version": "1.2.2",
Copy link
Member Author

Choose a reason for hiding this comment

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

We use an older version for dynamic builds, for compatibility with more systems. For example, the GitHub runners we're using did not have 1.2.5.

We retain the latest version for static builds, since compatibility isn't a concern.

@zanieb zanieb marked this pull request as ready for review March 10, 2025 16:38
Copy link
Collaborator

@geofft geofft left a comment

Choose a reason for hiding this comment

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

LGTM. At first glance I'm okay changing the default musl build to be dynamic and to make the static variant an explicit option, and I would assume that works fine with the release process since it at most adds more artifacts, some of which might get missed, but the existing set of artifacts will still be present (with a semantics change for the musl ones). But we should probably double-check whether uv and Rye are chill with the change before shipping. Are there any other high-profile users we should check, especially ones we know are using the musl builds? (PyOxidizer?)

Comment on lines +90 to +92
mipsel-unknown-linux-musl)
EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
;;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm assuming we were previously implicitly doing this for musl?

allowed_libraries.push(format!("libpython{}t.so.1.0", python_major_minor));
allowed_libraries.push(format!("libpython{}td.so.1.0", python_major_minor));
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

The changes from line 921 to here seem a little inconsistent, are all the changes here intentional?

Copy link
Member Author

@zanieb zanieb Mar 10, 2025

Choose a reason for hiding this comment

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

Oh thanks the changes around 924 are confusing? I'll revert that.

I can also probably make this bit conditional on the static build option.

Copy link
Member Author

Choose a reason for hiding this comment

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

@zanieb

This comment was marked as duplicate.

@zanieb
Copy link
Member Author

zanieb commented Mar 10, 2025

Are there any other high-profile users we should check, especially ones we know are using the musl builds? (PyOxidizer?)

I want to ack this, but I don't have a good answer. @ofek comes to mind as an automated consumer outside Astral.

@ofek
Copy link

ofek commented Mar 10, 2025

Changing the default to something that actually works is desirable in my opinion. It doesn't matter if there is new naming because in my projects that consume (PyApp & Hatch) there is no ability to use newer non-custom builds without a new release because PyApp has a script that generates the hardcoded source of truth, which Hatch then uses in a script to hardcode its source of truth. So I would just have to change the former script.

@zanieb
Copy link
Member Author

zanieb commented Mar 11, 2025

I considered a transition plan like

  • Continue to publish static builds as we are today
  • Create a duplicate archive with +static
  • Publish dynamic builds with +shared
  • Switch the default to the +shared variant, retaining a +shared duplicate archive
  • Drop the +shared archive

However, users still need to encode a specific version of python-build-standalone in which the default changed. After thinking about it more, I'm still leaning towards just changing the defaut.

@geofft
Copy link
Collaborator

geofft commented Mar 11, 2025

@ofek by "actually works" I assume you mean "able to dynamically load stuff"?

Then I think we should ship this and call it a bugfix and make sure we ship it at a time that people will be around on Discord etc. to help downstream folks that are broken by the change if any are actually broken by it.

We could also figure out how to make a static musl include a working libdl, which is probably easier than it would be for glibc because musl is generally less complex, but would still be a little bit of a research project.

@zanieb
Copy link
Member Author

zanieb commented Mar 11, 2025

The diff is not too complicated, so I'll just merge this and close #546

@zanieb zanieb merged commit 08c1fac into main Mar 11, 2025
134 checks passed
@zanieb zanieb deleted the zb/musl-dynamic branch March 11, 2025 16:50
zanieb added a commit that referenced this pull request Mar 11, 2025
Follows #541 

Tested locally with `just release-dry-run` and the artifacts on the
latest commit to `main`. Then used for
https://github.com/astral-sh/python-build-standalone/releases/tag/20250311
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc:musl platform:linux Specific to the Linux platform
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants