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

ENH: Don't add system library directories to rpath #160

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

DWesl
Copy link
Contributor

@DWesl DWesl commented Jul 31, 2022

Last of the patches from #73
Might close pypa/setuptools#3257
Might also close pypa/setuptools#3450, depending on what the actual problem looks like

Dual purposes here:

@isuruf
Copy link
Contributor

isuruf commented Jul 31, 2022

You are assuming that LIBDIR is in the dynamic linker's default search directories which is not true for some python distributions.

@DWesl
Copy link
Contributor Author

DWesl commented Jul 31, 2022

So this should stay a distribution patch? Should there be a note for packagers in the setuptools release notes that any patches on the CPython distutils should be duplicated on setuptools._distutils? (Yes this is implied by "setuptools now uses its own copy of distutils", but not everyone makes that connection).

@jaraco
Copy link
Member

jaraco commented Jul 31, 2022

So this should stay a distribution patch?

I’d say no. The goal of Setuptools adoption of distutils is to remove the need for distribution patches. If a distribution wishes to customize the behavior, Setuptools would like to devise a hook such that a distribution can configure that behavior and not have to patch it… although one supported way of customizing behavior is through a patch to sysconfig.

distutils/unixccompiler.py Outdated Show resolved Hide resolved
@DWesl
Copy link
Contributor Author

DWesl commented Aug 6, 2022

You are assuming that LIBDIR is in the dynamic linker's default search directories which is not true for some python distributions.

Is there a way to access the dynamic linker's default search directories from python?

@jaraco
Copy link
Member

jaraco commented Aug 13, 2022

You are assuming that LIBDIR is in the dynamic linker's default search directories which is not true for some python distributions.

Is there a way to access the dynamic linker's default search directories from python?

I don't understand this concern well enough to comment. I'm hoping @isuruf or someone else can comment.

Copy link
Member

@jaraco jaraco left a comment

Choose a reason for hiding this comment

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

Would it be possible to add a test or tests that capture the missed expectation? That way, even if this solution ends up not being the right one, a subsequent change would still honor the concerns this change is attempting to address?

@isuruf
Copy link
Contributor

isuruf commented Aug 13, 2022

Is there a way to access the dynamic linker's default search directories from python?

I don't think there is any. I guess there are couple of ways to allow this to work for your use-case.

  1. Introduce a sysconfig variable that downstream python maintainers add to their _sysconfigdata* files
  2. Check for LIBDIR and check that LIBDIR starts with /usr/lib.

@DWesl
Copy link
Contributor Author

DWesl commented Aug 15, 2022

Would it be possible to add a test or tests that capture the missed expectation? That way, even if this solution ends up not being the right one, a subsequent change would still honor the concerns this change is attempting to address?

I think so; the rough outline would be to create a C Extension module linked against some shared library in LIBDIR, check its RUNPATH (not sure how to do that) to make sure that's unset, then try to import it to check whether the runtime loader can find all the dependencies. The first and third bits might already be in the test suite.

Is there a way to access the dynamic linker's default search directories from python?

I don't think there is any. I guess there are couple of ways to allow this to work for your use-case.

  1. Introduce a sysconfig variable that downstream python maintainers add to their _sysconfigdata* files

  2. Check for LIBDIR and check that LIBDIR starts with /usr/lib.

That second one looks like a good starting point.

Copy link
Contributor Author

@DWesl DWesl left a comment

Choose a reason for hiding this comment

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

Changes suggested by black in CI

distutils/tests/test_build_ext.py Outdated Show resolved Hide resolved
distutils/unixccompiler.py Outdated Show resolved Hide resolved
distutils/unixccompiler.py Outdated Show resolved Hide resolved
Copy link
Contributor Author

@DWesl DWesl left a comment

Choose a reason for hiding this comment

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

More suggestions from black

distutils/tests/test_build_ext.py Outdated Show resolved Hide resolved
distutils/unixccompiler.py Show resolved Hide resolved
@jaraco
Copy link
Member

jaraco commented Sep 24, 2022

Is there a way to access the dynamic linker's default search directories from python?

I don't think there is any. I guess there are couple of ways to allow this to work for your use-case.

1. Introduce a sysconfig variable that downstream python maintainers add to their _sysconfigdata* files

2. Check for LIBDIR and check that LIBDIR starts with `/usr/lib`.

I like idea 1 best, because it gives the end user more control, but the second idea may be suitable too if it captures the essential concern.

@jaraco
Copy link
Member

jaraco commented Jun 27, 2024

What's the status of this PR? @DWesl are you still working on it? It looks like there was maybe just one lingering concern.

@DWesl
Copy link
Contributor Author

DWesl commented Jun 27, 2024

Could you remind me what that concern is? It's been a bit since I worked on this.

One concern would be all the new tests failing. I should figure out how to compile extensions on Linux, and whether renaming an so causes problems

@jaraco
Copy link
Member

jaraco commented Jun 28, 2024

After scanning through the messages, I guess I was mistaken. I see you addressed adding a test and of the two options suggested by isuruf, you elected the second. So now it's just a matter of making sure the tests pass. I'll work on it.

Copy link
Member

@jaraco jaraco left a comment

Choose a reason for hiding this comment

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

The tests don't appear to be passing. I see four failures in build_ext:

FAILED distutils/tests/test_build_ext.py::TestBuildExt::test_build_ext[False] - AssertionError: assert ('RPATH' not in '\nDynamic s...       0x0\n'
FAILED distutils/tests/test_build_ext.py::TestBuildExt::test_build_ext[True] - distutils.errors.LinkError: command '/usr/bin/gcc' failed with exit code 1
FAILED distutils/tests/test_build_ext.py::TestParallelBuildExt::test_build_ext[False] - AssertionError: assert ('RPATH' not in '\nDynamic s...       0x0\n'
FAILED distutils/tests/test_build_ext.py::TestParallelBuildExt::test_build_ext[True] - distutils.errors.LinkError: command '/usr/bin/gcc' failed with exit code 1

Can you investigate?

Last of the patches from pypa#73 
Might close pypa/setuptools#3257

Dual purposes here:
- On platforms like Cygwin that don't have `rpath`, try to avoid adding things to `rpath`
- Some distribution binary package makers require that no shared library list a system library directory (`/lib`, `/lib64`, `/usr/lib`, `/usr/lib64`) in its `rpath`; this patch simplifies the code to ensure the shared library can find its dependencies at runtime.
Avoids problems with odd LIBDIR
Package managers will be putting things in LIBDIR anyway,
so this should catch all use-cases I know of.
The check reference caused docutils problems with no ldesc.
The cygwinccompiler change produced a DeprecationWarning.
Cygwin separates import libraries from dynamic libraries (needed link time vs run time).
Let's see if I got the syntax right.

Next step will be hooking the temporary /tmp/libxx_z.so file handling into pytest's tempfile handling.
The old test seemed to pick up the 32-bit library, not the 64-bit one.
The new test should pick up the 64-bit one consistently when relevant.
DWesl added 4 commits July 3, 2024 10:48
Still need to get the test compiling in the alternate location.  Did I add -L/tmp?
I think /usr/lib/libz.so links to /usr/lib/libz.so.10, which in turn links to /usr/lib/libz.so.10.5.1 (numbers may differ), with the last being the actual library.  Ensure that is the one I copy for linking.
Maybe this will work better?  I should look into whether ELF `so`s store their basename.
@jaraco
Copy link
Member

jaraco commented Aug 27, 2024

@DWesl Let me know if you want to continue working on this, or if we should abandon it.

@DWesl
Copy link
Contributor Author

DWesl commented Aug 27, 2024

I don't understand why copying /usr/lib/libz.so to /tmp/libxx_z.so and trying to link with -L/tmp -lxx_z fails, so I'm not going to be a lot of help finishing.

If you know a library installed outside /usr/lib*/ on the Linux GHA runners, I can use that get a working test, otherwise it might be simplest to drop the test of rpath for non-system library directories.

EDIT: If you've already got a test of RPATH that this behavior won't break, then this PR is done barring removal of that part of the test.

@DWesl
Copy link
Contributor Author

DWesl commented Sep 3, 2024

Alternately, you could declare this PR tests the new behavior, and tests of old behavior are out of scope, at which point I delete the part of the test that checks the old behavior

Not the best solution, but I don't know how to check rpath in a manner that doesn't crash.
@DWesl
Copy link
Contributor Author

DWesl commented Sep 11, 2024

I dropped the test of old behavior, so that test passes, but now ruff fails with the extremely informative message "file would be changed". As I can't run ruff on my machine, I'm leaving that for someone else.

@jaraco
Copy link
Member

jaraco commented Sep 11, 2024

@isuruf or @lazka Would you be willing to check this change for reasonableness? Tests are passing, but I'm a little uneasy to accept it without a second set of eyes more familiar with the domain. Thanks.

@lazka
Copy link
Contributor

lazka commented Sep 12, 2024

I don't have much experience with rpath (and why one would set it via setuptools), but I wonder if setup.py explicitly requests rpath to be set why remove some of them again? And what is so special about LIBDIR, and /usr/lib with regards to rpath (what about /usr/lib64 used by fedora?)? and the tests seem unfinished (parametrize with one value, left over sleep call)

Regarding the cygwin part here, I was trying to fix it back then in #150 but missed that cygwincompiler is not actually used outside of tests (see #185) so I think we should move the cygwin check to ignore runtime_library_dirs to unixcompiler as was initially intended.

@isuruf
Copy link
Contributor

isuruf commented Sep 13, 2024

if setup.py explicitly requests rpath to be set why remove some of them again?

I think the main reason is that some distributions fail when creating a RPM/DEB package when there is a rpath for the system library dir. See the issues that OP posted. Usually, packages in setup.py add this rpath so that when building packages locally, the package works without having to set LD_LIBRARY_PATH which is a bad thing to do. Ideally packages should only add RPATH if the path is not a package in the dynamic linker's path.

And what is so special about LIBDIR, and /usr/lib with regards to rpath (what about /usr/lib64 used by fedora?)?

/usr/lib and /usr/lib64 are in the dynamic linker's default paths to check. RPATHs just adds to the paths to check, so technically adding them is not necessary.

I'm not sure if removing these directories is the correct thing to do, but I don't think it would break anything.

@DWesl
Copy link
Contributor Author

DWesl commented Sep 17, 2024

if setup.py explicitly requests rpath to be set why remove some of them again?

I think the main reason is that some distributions fail when creating a RPM/DEB package when there is a rpath for the system library dir. See the issues that OP posted. Usually, packages in setup.py add this rpath so that when building packages locally, the package works without having to set LD_LIBRARY_PATH which is a bad thing to do. Ideally packages should only add RPATH if the path is not a package in the dynamic linker's path.

Specifically, it looks like it is distrubution policy not to release a package with the system library paths in the rpath of any shared library, because the system library paths are always searched outside certain specific situations. I suspect they don't like the redundancy, as the space gains would be minor.

And what is so special about LIBDIR, and /usr/lib with regards to rpath (what about /usr/lib64 used by fedora?)?

/usr/lib and /usr/lib64 are in the dynamic linker's default paths to check. RPATHs just adds to the paths to check, so technically adding them is not necessary.

I'm not sure if removing these directories is the correct thing to do, but I don't think it would break anything.

I think the LIBDIR check is a simple check for whether python is a system python, so this doesn't break pythons installed to alternate directories not in the default search paths. That seems to have been inherited from the Fedora patch mentioned in #73.

Since my motivation (crashes on Cygwin) seems to have been alleviated in other ways, the main alternatives would be leaving it to distributions that have such policies to patch setuptools on their own.

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