From 2323b7654b01deb7a2ff035e6c90352fa1b4d4d2 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 28 Nov 2024 12:57:18 +0100 Subject: [PATCH] Print a warning when mixing $EBPYTHONPREFIXES and $PYTHONPATH modules $PYTHONPATH takes precedence which might lead to unexpected or wrong packages being used especially when a dependency includes a newer package than another dependency. As it is very hard to find this as the cause print a warning when this situation is found. --- easybuild/easyblocks/generic/pythonpackage.py | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/generic/pythonpackage.py b/easybuild/easyblocks/generic/pythonpackage.py index 6318a88f67..117ba90d95 100644 --- a/easybuild/easyblocks/generic/pythonpackage.py +++ b/easybuild/easyblocks/generic/pythonpackage.py @@ -47,7 +47,7 @@ from easybuild.framework.easyconfig.default import DEFAULT_CONFIG from easybuild.framework.easyconfig.templates import TEMPLATE_CONSTANTS from easybuild.framework.extensioneasyblock import ExtensionEasyBlock -from easybuild.tools.build_log import EasyBuildError, print_msg +from easybuild.tools.build_log import EasyBuildError, print_msg, print_warning from easybuild.tools.config import build_option from easybuild.tools.filetools import change_dir, mkdir, remove_dir, symlink, which from easybuild.tools.modules import get_software_root @@ -508,6 +508,41 @@ def set_pylibdirs(self): self.all_pylibdirs = get_pylibdirs(python_cmd=self.python_cmd) self.pylibdir = self.all_pylibdirs[0] + def verify_eb_pythonprefixes(self): + """Warn if mixing dependency modules with $PYTHONPATH and $EBPYTHONPREFIXES""" + python_root = get_software_root('Python') + # Our site-customize is installed into the Python module. If that isn't loaded there is nothing to check. + if not python_root: + return + pythonpaths_var = os.environ.get('PYTHONPATH') + if not (os.environ.get(EBPYTHONPREFIXES) and pythonpaths_var): + self.log.debug('At most one of $PYTHONPATH and $%s is set. Skipping conflict check.', EBPYTHONPREFIXES) + return + # Check if any entry from $PYTHONPATH is from a dependent module. + # Resolve symlinks (only works for existing paths) to match the strings later. + pythonpaths = [os.path.realpath(path) for path in pythonpaths_var.split(os.path.pathsep) + if os.path.exists(path)] + # Check only paths that could be replaced by $EBPYTHONPREFIXES to avoid false positives + pythonpaths = [path for path in pythonpaths if path.endswith(os.sep + self.pylibdir)] + self.log.debug('Keeping %s from $PYTHONPATH=%s for checking', pythonpaths, pythonpaths_var) + + def is_in_pythonpaths(dependency): + if dependency['name'] == 'Python': + return False # Ignore the Python module + try: + root = os.path.realpath(get_software_root(dependency['name'])) + except OSError: + return False + # Search for the path or a subdirectory of it + return any(path == root or path.startswith(root + os.sep) for path in pythonpaths) + + conflicts = [dep['name'] for dep in self.cfg.dependencies() if is_in_pythonpaths(dep)] + if conflicts: + print_warning('The following modules set $PYTHONPATH but $%s is also used: %s\n' + 'Mixing such modules might cause unexpected results during the build of Python packages. ' + 'Try rebuilding the module files to use either $PYTHONPATH or $%s.', + EBPYTHONPREFIXES, ', '.join(conflicts), EBPYTHONPREFIXES) + def prepare_python(self): """Python-specific preparations.""" @@ -562,8 +597,8 @@ def prepare_python(self): self.log.info("Python command being used: %s", self.python_cmd) if self.python_cmd: - # set Python lib directories self.set_pylibdirs() + self.verify_eb_pythonprefixes() def _should_unpack_source(self): """Determine whether we need to unpack the source(s)"""