python: fix and refactor RPATHs for compiled extensions#16527
python: fix and refactor RPATHs for compiled extensions#16527skosukhin wants to merge 5 commits intospack:developfrom
Conversation
Magic? $ otool -L ./lib/python3.7/site-packages/_yaml.cpython-37m-darwin.so
./lib/python3.7/site-packages/_yaml.cpython-37m-darwin.so:
/Users/Adam/spack/opt/spack/darwin-catalina-x86_64/clang-11.0.3-apple/libyaml-0.2.4-hreynrt3r54ee6ieqqalj2buulmhdf5l/lib/libyaml-0.2.dylib (compatibility version 3.0.0, current version 3.8.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)Btw, |
|
@adamjstewart magic indeed. I will check, which versions of Python have this and update this PR accordingly. |
|
For Python 2, the aforementioned magic was introduced in v2.7.3, inadvertently dropped in v2.7.4 and reintroduced in v2.7.6. |
|
I don't think that we need any special treatment for |
70c4d4a to
e3ebb9a
Compare
| pythonpath = ':'.join(python_paths) | ||
| env.set('PYTHONPATH', pythonpath) | ||
|
|
||
| # We want to make sure that the extensions are compiled and linked with |
There was a problem hiding this comment.
I wonder whether we should do the following only if dependent_spec.package.extends(self.spec).
|
Ping @scheibelp, any idea who wrote the existing logic in |
|
@adamjstewart I think the existing logic was introduced in #2506. |
|
@skosukhin want to add yourself to the list of |
|
Btw, I noticed that some Python packages use the compiler registered in distutils at build-time. Worse yet, packages like |
|
@adamjstewart it's been a while I go but I will try to recall the details.
So, basically the answer to your question is yes, in one way or another we temporarily re-add Spack's compiler wrappers during Spack builds. |
| # distutils.sysconfig starts with the path to the real compiler | ||
| # that will be used for the compilation of the dependent | ||
| # package. This should sufficiently cover cases A and B, as | ||
| # well as case C in some rare cases. |
There was a problem hiding this comment.
What do you mean by "in some rare cases"? I assume this would be normal when the external python was built with a different compiler than what is used for the dependent.
There was a problem hiding this comment.
To my understanding, it is safe to modify and set LDSHARED only when the output of the command
$ python -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDSHARED"))'`starts with the path to the real compiler that is used for the extension. Here is a couple of examples of the output of the command:
x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -Wdate-time -D_FORTIFY_SOURCE=2 -g -fdebug-prefix-map=/build/python2.7-V2e5UW/python2.7-2.7.13=. -fstack-protector-strong -Wformat -Werror=format-securityThe beginning of the string is not /usr/bin/gcc, which I might want to use in this case. Ok, we could compare the output of
$ python -c "import os; print(os.path.realpath('$(which x86_64-linux-gnu-gcc)'))"and
$ python -c "import os; print(os.path.realpath('/usr/bin/gcc'))"and see that the result is the same and still make a substitution in LSDHARED. But what about this output on another machine:
gcc -pthread -sharedCan we use the same logic? Can we be sure that gcc returned by the which command is the compiler that was used for the building of Python? It might also be the case that the compiler in use is not /usr/bin/gcc and therefore the real paths won't match anyway. Can we be sure that the compiler in use will accept the arguments listed in LDSHARED? What if we are trying to build an extension using Intel compiler? There are many edge cases, especially when we are dealing with an external Python. I would rather not introduce complex logic to cover them but keep the simple one that is implemented here. However, this simple implementation will rarely work with external installations of Python because the default value of LDSHARED of a system installation rarely starts with the full path to the compiler in use. My recommendation for the users would be: it's fine to use an external installation of Python if it's always just a build dependency in your software tree but you should really consider building Python with Spack if you want to install compiled extensions for it.
|
@adamjstewart I haven't followed the recent developments. Can you, please, tell whether this PR is still relevant? I saw that you tried to solve the RPATH problem in a different way. |
|
The logic I introduced in #20765 doesn't seem to be working, at least for some packages. If merging this PR allows us to use Spack's compiler wrappers for all Python packages, that would be fantastic. I must admit I don't fully understand the logic that you introduced, or the logic it is designed to replace. Let me see if I can convince anyone else to review this PR. If not, I'll try building my Python stack with this PR (can you rebase on develop?) to make sure it doesn't break anything. |
I assigned myself but lost track of it. It is back on my radar.
I'll take a shot at this:
Generally I prefer this PR because I think it resolves some issues with redundancy. I'm not immediately sure that this would succeed where #20765 has not e.g. #20765 (comment) (since in many ways I think these approaches would have a similar effect, at least for Spack-built Python). FWIW I do think this PR would avoid some possible issues when using an external Python (a combination of Spack RPATHs with a different compiler could lead to compilation problems). I'd like to try building a few packages that people were trying in #20765, but with this PR, I'll have a chance to do that on Tuesday (Jan. 19). |
…one for MacOS in distutils.sysconfig.customize_compiler().
|
I wonder what should be the condition that triggers the additional logic in
What do you think? |
258cf7d to
485d566
Compare
|
That is a great question. I think either 2 or maybe 3 makes the most sense. There is a 5th option where you only do it if type=build/run, but that's not drastically different than 3 and 4. If a package has a build dep on Python and it doesn't use distutils, that isn't really a problem even if you patch it. I guess I don't fully understand your concern:
Why would it do that? Can we make it not do that? |
I may be confused because I think we already call |
|
@scheibelp yes, we call @adamjstewart I wanted to warn the users for whatever reason but then I realised that the warning was almost always emitted if Anyway, this solution is overcomplicated (as the original one). I think #21149 is better. Please, take a look. |
I think I understand: if we have a build dependency on Python but we are not building a Python extension, then there is no need to set (also I'm looking at #21149) |
Most of the
PythonPackages usePythonPackage.setup_py(), which is different fromsetup_pythat is set inPython.setup_dependent_package(). Only the latter is run withLDSHAREDenvironment variable, which is set to the value that is supposed to enforce the linking of the compiled extensions with the Spack wrapper. This means that most of the extensions are currently linked with the real compiler, not the wrapper.I think that the logic in
PythonPackage.setup_py()and inPython.setup_dependent_package()needs to be the same without duplication. This is out of the scope of this PR but if there is an agreement on how to refactor this, I could add it here as well.@adamjstewart I am curious how you managed to build
py-pyyaml+libyamlafter py-pyyaml: add new version and variant #16301. In my case, since the linking is done without the wrapper, the linker cannot find-lyaml.Maybe I missed something that would be relevant for this PR.
To cover both of the aforementioned cases,
LDSHAREDneeds to be set inPython.setup_dependent_build_environment()(if at all). This is part of this PR.The current logic with a JSON file is too complicated. This PR replaces it with a more simple solution. Hopefully, the comments in the code clarify the implementation details well enough.