From 597ad7cb9cc7605561c784c36ec0a428c1711904 Mon Sep 17 00:00:00 2001 From: Alex Richert Date: Tue, 5 Dec 2023 16:04:23 -0600 Subject: [PATCH 1/6] Add util to check executables/shared libs for missing shared libs --- util/ldd_check.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 util/ldd_check.py diff --git a/util/ldd_check.py b/util/ldd_check.py new file mode 100755 index 000000000..f9bba9245 --- /dev/null +++ b/util/ldd_check.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +# Whitelist file patterns (checked with re.match()) that will be satisfied by +# compiler & MPI modules (though arguably these should be added as extra +# rpaths). +whitelist = [ + "^libmkl.+", +] + +######## + +import argparse +import glob +import os +import platform +import re +import subprocess +import sys + +parser = argparse.ArgumentParser(description="Check executables and shared libraries for missing dependencies") + +parser.add_argument( + "path", + nargs="?", + default=os.getcwd(), + help="Spack environment path ($SPACK_ENV) that contains install/ subdirectory (default: current directory)", +) +parser.add_argument( + "--progress", + "-p", + action="store_true", + help="Print progress to stderr", +) + +args = parser.parse_args() + +platform = platform.system() +if platform=="Linux": + ldd_cmd = "ldd" + error_pattern = " => not found" + getlibname = lambda line: re.findall("^[^ ]+", line)[0] +elif platform=="Darwin": + print("macOS not yet supported", file=sys.stderr) + sys.exit(1) +else: + print(f"Platform '{platform}' not supported", file=sys.stderr) + sys.exit(1) + +searchpath = os.path.join(args.path, "install") + +bin_list = glob.glob(os.path.join(searchpath, "*/*/*/bin/*")) +dlib_list = glob.glob(os.path.join(searchpath, "*/*/*/lib*/*.{so,dylib}")) + +master_list = bin_list + dlib_list + +assert master_list, "No files found! Check directory and ensure it contains install/ subdirectory" + +for i in range(len(master_list)): + file_to_check = master_list[i] + if args.progress: print(f"\rProgress: {i+1}/{len(master_list)}", file=sys.stderr, end="") + p = subprocess.Popen([ldd_cmd, file_to_check], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + raw_output, null = p.communicate() + output = raw_output.decode(sys.stdout.encoding).strip() + if error_pattern in output: + missing_set = set([getlibname(l.strip()) for l in output.split("\n") if error_pattern in l]) + missing_list = [l for l in missing_set if not any([re.match(p, l) for p in whitelist])] + if not missing_list: continue + missing_output = ",".join(sorted(missing_list)) + print(f"\rWARNING: File {file_to_check} contains the following missing libraries: {missing_output}") + +if args.progress: print() From 0741eba0aa71bd0157fa0fd76f46f91af2d9d615 Mon Sep 17 00:00:00 2001 From: Alex Richert Date: Tue, 5 Dec 2023 16:14:54 -0600 Subject: [PATCH 2/6] add ldd_check.py to CI --- .github/workflows/ubuntu-ci-x86_64.yaml | 1 + util/ldd_check.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ubuntu-ci-x86_64.yaml b/.github/workflows/ubuntu-ci-x86_64.yaml index 88b1b9584..d9997ae5c 100644 --- a/.github/workflows/ubuntu-ci-x86_64.yaml +++ b/.github/workflows/ubuntu-ci-x86_64.yaml @@ -145,6 +145,7 @@ jobs: # Next steps: synchronize source and build cache to a central/combined mirror? echo "Next steps ..." + ${SPACK_STACK_DIR}/util/ldd_check.py $SPACK_ENV 2>&1 | tee log.ldd_check spack clean -a spack module tcl refresh -y spack stack setup-meta-modules diff --git a/util/ldd_check.py b/util/ldd_check.py index f9bba9245..155af167f 100755 --- a/util/ldd_check.py +++ b/util/ldd_check.py @@ -55,6 +55,8 @@ assert master_list, "No files found! Check directory and ensure it contains install/ subdirectory" +iret = 0 + for i in range(len(master_list)): file_to_check = master_list[i] if args.progress: print(f"\rProgress: {i+1}/{len(master_list)}", file=sys.stderr, end="") @@ -67,5 +69,8 @@ if not missing_list: continue missing_output = ",".join(sorted(missing_list)) print(f"\rWARNING: File {file_to_check} contains the following missing libraries: {missing_output}") + iret = 1 if args.progress: print() + +sys.exit(iret) From e8029f1589801e27e26a5fda83505ba0f021a4a1 Mon Sep 17 00:00:00 2001 From: AlexanderRichert-NOAA Date: Wed, 6 Dec 2023 00:48:24 +0000 Subject: [PATCH 3/6] add ignore option and ignore libifcore.so --- util/ldd_check.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/ldd_check.py b/util/ldd_check.py index 155af167f..45867a755 100755 --- a/util/ldd_check.py +++ b/util/ldd_check.py @@ -5,6 +5,7 @@ # rpaths). whitelist = [ "^libmkl.+", + "^libifcore.so.*", ] ######## @@ -31,9 +32,17 @@ action="store_true", help="Print progress to stderr", ) +parser.add_argument( + "--ignore", + "-i", + action="append", + help="Ignore pattern (Python re expression, e.g., '^libfoo.+')", +) args = parser.parse_args() +whitelist.extend(args.ignore) + platform = platform.system() if platform=="Linux": ldd_cmd = "ldd" From d5f30a51a1c6ffad3e517b56747daef853c83220 Mon Sep 17 00:00:00 2001 From: Alex Richert Date: Thu, 7 Dec 2023 17:22:00 -0600 Subject: [PATCH 4/6] add ldd_check.py to utils docs --- doc/source/Utilities.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/source/Utilities.rst b/doc/source/Utilities.rst index fad759688..e5db3a19e 100644 --- a/doc/source/Utilities.rst +++ b/doc/source/Utilities.rst @@ -28,6 +28,20 @@ check_permissions.sh The utility located at util/check_permissions.sh can be run inside any spack-stack environment directory intended for multiple users (i.e., on an HPC or cloud platform). It will return errors if the environment directory is inaccessible to non-owning users and groups (i.e., if o+rx not set), as well as if any directories or files have permissions that make them inaccessible to other users. +.. _LDD_Checker: + +------------------------------ +ldd_check.py +------------------------------ + +The utils/ldd_check.py utility should be run for new installations to ensure that no shared library or executable that uses shared libraries is missing a shared library dependency. If the script returns a warning for a given file, this may indicate that Spack's RPATH substitution has not been properly applied. In some instances, missing library dependencies may not indicate a problem, such as a library that is intended to be found through $LD_LIBRARY_PATH after, say, a compiler or MPI environment module is loaded. Though these paths should probably also be RPATH-ified, such instances of harmless missing dependencies may be ignored with ldd_check.py's ``--ignore`` option by specifying a Python regular expression to be excluded from consideration (see example below), or can be permanently whitelisted by modifying the ``whitelist`` variable at the top of the ldd_check.py script itself (in which case please submit a PR). The script searches the 'install/' subdirectory of a given path and runs ``ldd`` on all shared objects. The base path to be search can be specified as a lone positional argument, and by default is the current directory. In practice, this should be ``$SPACK_ENV`` for the environment in question. + +.. code-block:: console + + cd $SPACK_ENV && ../../utils/ldd_check.py + # - OR - + utils/ldd_check.py $SPACK_ENV --ignore '^libfoo.+' # check for missing shared dependencies, but ignore missing libfoo* + .. _Acorn_Utilities: ------------------------------ From 60123e6bcb5dfc567e08bbe1ced2bb9c361d2fb9 Mon Sep 17 00:00:00 2001 From: Alex Richert Date: Thu, 7 Dec 2023 17:52:56 -0600 Subject: [PATCH 5/6] fix doco and bug for ldd_check.py --- doc/source/Utilities.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/Utilities.rst b/doc/source/Utilities.rst index e5db3a19e..9e8a81a39 100644 --- a/doc/source/Utilities.rst +++ b/doc/source/Utilities.rst @@ -34,13 +34,13 @@ The utility located at util/check_permissions.sh can be run inside any spack-sta ldd_check.py ------------------------------ -The utils/ldd_check.py utility should be run for new installations to ensure that no shared library or executable that uses shared libraries is missing a shared library dependency. If the script returns a warning for a given file, this may indicate that Spack's RPATH substitution has not been properly applied. In some instances, missing library dependencies may not indicate a problem, such as a library that is intended to be found through $LD_LIBRARY_PATH after, say, a compiler or MPI environment module is loaded. Though these paths should probably also be RPATH-ified, such instances of harmless missing dependencies may be ignored with ldd_check.py's ``--ignore`` option by specifying a Python regular expression to be excluded from consideration (see example below), or can be permanently whitelisted by modifying the ``whitelist`` variable at the top of the ldd_check.py script itself (in which case please submit a PR). The script searches the 'install/' subdirectory of a given path and runs ``ldd`` on all shared objects. The base path to be search can be specified as a lone positional argument, and by default is the current directory. In practice, this should be ``$SPACK_ENV`` for the environment in question. +The util/ldd_check.py utility should be run for new installations to ensure that no shared library or executable that uses shared libraries is missing a shared library dependency. If the script returns a warning for a given file, this may indicate that Spack's RPATH substitution has not been properly applied. In some instances, missing library dependencies may not indicate a problem, such as a library that is intended to be found through $LD_LIBRARY_PATH after, say, a compiler or MPI environment module is loaded. Though these paths should probably also be RPATH-ified, such instances of harmless missing dependencies may be ignored with ldd_check.py's ``--ignore`` option by specifying a Python regular expression to be excluded from consideration (see example below), or can be permanently whitelisted by modifying the ``whitelist`` variable at the top of the ldd_check.py script itself (in which case please submit a PR). The script searches the 'install/' subdirectory of a given path and runs ``ldd`` on all shared objects. The base path to be search can be specified as a lone positional argument, and by default is the current directory. In practice, this should be ``$SPACK_ENV`` for the environment in question. .. code-block:: console - cd $SPACK_ENV && ../../utils/ldd_check.py + cd $SPACK_ENV && ../../util/ldd_check.py # - OR - - utils/ldd_check.py $SPACK_ENV --ignore '^libfoo.+' # check for missing shared dependencies, but ignore missing libfoo* + util/ldd_check.py $SPACK_ENV --ignore '^libfoo.+' # check for missing shared dependencies, but ignore missing libfoo* .. _Acorn_Utilities: From 29dc867bb1bb23cf28c924ba1c920ded7d19d908 Mon Sep 17 00:00:00 2001 From: Alex Richert <82525672+AlexanderRichert-NOAA@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:57:18 -0800 Subject: [PATCH 6/6] Update ldd_check.py --- util/ldd_check.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/ldd_check.py b/util/ldd_check.py index 45867a755..af6d3fa77 100755 --- a/util/ldd_check.py +++ b/util/ldd_check.py @@ -41,7 +41,8 @@ args = parser.parse_args() -whitelist.extend(args.ignore) +if args.ignore: + whitelist.extend(args.ignore) platform = platform.system() if platform=="Linux":