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

enhance get_software_libdir to return full paths if requested #4699

Merged
merged 2 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 46 additions & 40 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -1697,7 +1697,7 @@ def get_software_root(name, with_env_var=False):
return res


def get_software_libdir(name, only_one=True, fs=None):
def get_software_libdir(name, only_one=True, fs=None, full_path=False):
"""
Find library subdirectories for the specified software package.

Expand All @@ -1708,51 +1708,57 @@ def get_software_libdir(name, only_one=True, fs=None):
:param name: name of the software package
:param only_one: indicates whether only one lib path is expected to be found
:param fs: only retain library subdirs that contain one of the files in this list
:param full_path: Include the software root in the returned path, or just return the subfolder found
"""
lib_subdirs = ['lib', 'lib64']
root = get_software_root(name)
res = []
if root:
for lib_subdir in lib_subdirs:
lib_dir_path = os.path.join(root, lib_subdir)
if os.path.exists(lib_dir_path):
# take into account that lib64 could be a symlink to lib (or vice versa)
# see https://github.com/easybuilders/easybuild-framework/issues/3139
if any(os.path.samefile(lib_dir_path, os.path.join(root, x)) for x in res):
_log.debug("%s is the same as one of the other paths, so skipping it", lib_dir_path)

elif fs is None or any(os.path.exists(os.path.join(lib_dir_path, f)) for f in fs):
_log.debug("Retaining library subdir '%s' (found at %s)", lib_subdir, lib_dir_path)
res.append(lib_subdir)

elif build_option('extended_dry_run'):
res.append(lib_subdir)
break

# if no library subdir was found, return None
if not res:
return None
if only_one:
if len(res) == 1:
res = res[0]
else:
if fs is None and len(res) == 2:
# if both lib and lib64 were found, check if only one (exactly) has libraries;
# this is needed for software with library archives in lib64 but other files/directories in lib
lib_glob = ['*.%s' % ext for ext in ['a', get_shared_lib_ext()]]
has_libs = [any(glob.glob(os.path.join(root, subdir, f)) for f in lib_glob) for subdir in res]
if has_libs[0] and not has_libs[1]:
return res[0]
elif has_libs[1] and not has_libs[0]:
return res[1]

raise EasyBuildError("Multiple library subdirectories found for %s in %s: %s",
name, root, ', '.join(res))
return res
else:
if not root:
# return None if software package root could not be determined
return None

found_subdirs = []
for lib_subdir in lib_subdirs:
lib_dir_path = os.path.join(root, lib_subdir)
if os.path.exists(lib_dir_path):
# take into account that lib64 could be a symlink to lib (or vice versa)
# see https://github.com/easybuilders/easybuild-framework/issues/3139
if any(os.path.samefile(lib_dir_path, os.path.join(root, x)) for x in found_subdirs):
_log.debug("%s is the same as one of the other paths, so skipping it", lib_dir_path)

elif fs is None or any(os.path.exists(os.path.join(lib_dir_path, f)) for f in fs):
_log.debug("Retaining library subdir '%s' (found at %s)", lib_subdir, lib_dir_path)
found_subdirs.append(lib_subdir)

elif build_option('extended_dry_run'):
found_subdirs.append(lib_subdir)
break

# if no library subdir was found, return None
if not found_subdirs:
return None
if full_path:
res = [os.path.join(root, subdir) for subdir in found_subdirs]
else:
res = found_subdirs
if only_one:
if len(res) == 1:
res = res[0]
else:
if fs is None and len(res) == 2:
# if both lib and lib64 were found, check if only one (exactly) has libraries;
# this is needed for software with library archives in lib64 but other files/directories in lib
lib_glob = ['*.%s' % ext for ext in ['a', get_shared_lib_ext()]]
has_libs = [any(glob.glob(os.path.join(root, subdir, f)) for f in lib_glob)
for subdir in found_subdirs]
if has_libs[0] and not has_libs[1]:
return res[0]
if has_libs[1] and not has_libs[0]:
return res[1]

raise EasyBuildError("Multiple library subdirectories found for %s in %s: %s",
name, root, ', '.join(found_subdirs))
return res


def get_software_version_env_var_name(name):
"""Return name of environment variable for software root."""
Expand Down
29 changes: 20 additions & 9 deletions test/framework/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ def test_get_software_root_version_libdir(self):
self.assertEqual(get_software_root(name), root)
self.assertEqual(get_software_version(name), version)
self.assertEqual(get_software_libdir(name), 'lib')
self.assertEqual(get_software_libdir(name, full_path=True), os.path.join(root, 'lib'))

os.environ.pop('EBROOT%s' % env_var_name)
os.environ.pop('EBVERSION%s' % env_var_name)
Expand All @@ -694,50 +695,60 @@ def test_get_software_root_version_libdir(self):
root = os.path.join(tmpdir, name)
mkdir(os.path.join(root, 'lib64'))
os.environ['EBROOT%s' % env_var_name] = root

def check_get_software_libdir(expected, **additional_args):
self.assertEqual(get_software_libdir(name, **additional_args), expected)
if isinstance(expected, list):
expected = [os.path.join(root, d) for d in expected]
elif expected:
expected = os.path.join(root, expected)
self.assertEqual(get_software_libdir(name, full_path=True, **additional_args), expected)

write_file(os.path.join(root, 'lib', 'libfoo.a'), 'foo')
self.assertEqual(get_software_libdir(name), 'lib')
check_get_software_libdir('lib')

remove_file(os.path.join(root, 'lib', 'libfoo.a'))

# also check vice versa with *shared* library in lib64
shlib_ext = get_shared_lib_ext()
write_file(os.path.join(root, 'lib64', 'libfoo.' + shlib_ext), 'foo')
self.assertEqual(get_software_libdir(name), 'lib64')
check_get_software_libdir('lib64')

remove_file(os.path.join(root, 'lib64', 'libfoo.' + shlib_ext))

# check expected result of get_software_libdir with multiple lib subdirs
self.assertErrorRegex(EasyBuildError, "Multiple library subdirectories found.*", get_software_libdir, name)
self.assertEqual(get_software_libdir(name, only_one=False), ['lib', 'lib64'])
check_get_software_libdir(only_one=False, expected=['lib', 'lib64'])

# only directories containing files in specified list should be retained
write_file(os.path.join(root, 'lib64', 'foo'), 'foo')
self.assertEqual(get_software_libdir(name, fs=['foo']), 'lib64')
check_get_software_libdir(fs=['foo'], expected='lib64')

# duplicate paths due to symlink get filtered
remove_dir(os.path.join(root, 'lib64'))
symlink(os.path.join(root, 'lib'), os.path.join(root, 'lib64'))
self.assertEqual(get_software_libdir(name), 'lib')
check_get_software_libdir('lib')

# same goes for lib symlinked to lib64
remove_file(os.path.join(root, 'lib64'))
remove_dir(os.path.join(root, 'lib'))
mkdir(os.path.join(root, 'lib64'))
symlink(os.path.join(root, 'lib64'), os.path.join(root, 'lib'))
# still returns 'lib' because that's the first subdir considered
self.assertEqual(get_software_libdir(name), 'lib')
check_get_software_libdir('lib')

# clean up for previous tests
os.environ.pop('EBROOT%s' % env_var_name)

# if root/version for specified software package can not be found, these functions should return None
self.assertEqual(get_software_root('foo'), None)
self.assertEqual(get_software_version('foo'), None)
self.assertEqual(get_software_libdir('foo'), None)
self.assertEqual(get_software_root(name), None)
self.assertEqual(get_software_version(name), None)
check_get_software_libdir(None)

# if no library subdir is found, get_software_libdir should return None
os.environ['EBROOTFOO'] = tmpdir
self.assertEqual(get_software_libdir('foo'), None)
self.assertEqual(get_software_libdir('foo', full_path=True), None)
os.environ.pop('EBROOTFOO')

shutil.rmtree(tmpdir)
Expand Down
Loading