Skip to content

Commit

Permalink
Merge pull request #4699 from Flamefire/fullpath-libdir
Browse files Browse the repository at this point in the history
enhance `get_software_libdir` to return full paths if requested
  • Loading branch information
bartoldeman authored Dec 9, 2024
2 parents ede3568 + f35ae5e commit b07033b
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 49 deletions.
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

0 comments on commit b07033b

Please sign in to comment.