12from SCons.Script
import AddOption, GetOption, BUILD_TARGETS
14from SCons.Action
import Action
19def get_dtneeded(filename):
20 """Use readelf (from binutils) to get a list of required libraries for a
21 given shared object file. This will just look at the NEEDED entries in the
22 dynamic table so
in contrast to ldd it will
not resolve child
23 dependencies recursively. It will also just
return the library names,
not in
24 which directory they might be found.
"""
27 re_readelf = re.compile(
r"^\s*0x0.*\(NEEDED\).*\[(.*)\]\s*$", re.M)
28 readelf_out = subprocess.check_output([
"readelf",
"-d", filename], stderr=subprocess.STDOUT,
29 env=dict(os.environ, LC_ALL=
"C"), encoding=
"utf-8")
30 needed_entries = re_readelf.findall(readelf_out)
32 except Exception
as e:
33 print(f
"Could not get dependencies for library {filename}: {e}")
37def get_env_list(env, key):
38 """Get a list from the environment by substituting all values and converting
39 them to str. For example to get the list of libraries to be linked in
41 >>> get_env_list(env,
"LIBS")
42 [
"framework",
"framework_dataobjects"]
44 return map(str, env.subst_list(key)[0])
47def get_package(env, node):
48 """Determine the package of a node by looking at it's sources
49 Hopefully the sources will be inside the build directory so look for the
50 first one there
and determine the package name
from its name.
"""
51 builddir = env.Dir("$BUILDDIR").get_abspath()
52 for n
in node.sources:
53 fullpath = n.get_abspath()
54 if fullpath.startswith(builddir):
56 components = os.path.relpath(fullpath, builddir).split(os.path.sep)
59 if len(components) == 1:
60 return components[0].split(
"_", 2)[0]
67def print_libs(title, text, pkg, lib, libs):
68 """Print information on extra/missing libraries"""
69 for library
in sorted(libs):
70 print(f
"{title}:{pkg}:{lib} -> {library} ({text})")
74 """Check library dependencies. This is called as a builder hence the target and
75 source arguments which are ignored."""
77 libdir = env.Dir("$LIBDIR").get_abspath()
79 stack = [env.Dir(
"$LIBDIR"), env.Dir(
"$MODDIR")]
85 for node
in dirnode.all_children():
86 if isinstance(node, Node.FS.Dir):
89 elif node.has_explicit_builder()
and str(node).endswith(
".so"):
93 if os.path.basename(node.get_abspath()) ==
"libdataobjects.so":
96 name = os.path.basename(fullname)
98 if "modules" in fullname.split(os.path.sep):
99 name =
"modules/" + name
101 pkg = get_package(env, node)
103 libraries.append((pkg, name, node))
106 for pkg, lib, node
in sorted(libraries):
108 given = get_env_list(node.get_build_env(),
"$LIBS")
110 needed = get_dtneeded(node.get_abspath())
115 def remove_libprefix(x):
116 """small helper to get rid of lib* prefix for requirements as SCons
117 seems to do this transparently as well
"""
118 if x.startswith(
"lib"):
119 print_libs(
"LIB_WARNING",
"dependency given as lib*, please remove "
120 "'lib' prefix in SConscript", pkg, str(node), [x])
125 given = map(remove_libprefix, given)
140 given_internal = {f
"lib{library}.so" for library
in given
if os.path.exists(os.path.join(libdir,
141 f
"lib{library}.so"))}
142 needed_internal = {library
for library
in needed
if os.path.exists(os.path.join(libdir, library))}
146 if GetOption(
"libcheck_extra"):
147 extra = given_internal - needed_internal
148 print_libs(
"LIB_EXTRA",
"dependency not needed and can be removed from SConscript", pkg, lib, extra)
150 if GetOption(
"libcheck_missing"):
151 missing = needed_internal - given_internal
152 print_libs(
"LIB_MISSING",
"library needed directly, please add to SConscript", pkg, lib, missing)
154 print(
"*** finished checking library dependencies")
158 AddOption(
"--check-extra-libraries", dest=
"libcheck_extra", action=
"store_true", default=
False,
159 help=
"if given all libraries will be checked for dependencies in "
160 "the SConscript which are not actually needed")
161 AddOption(
"--check-missing-libraries", dest=
"libcheck_missing", action=
"store_true", default=
False,
162 help=
"if given all libraries will be checked for missing direct dependencies after build")
164 if env.GetOption(
"libcheck_extra")
or env.GetOption(
"libcheck_missing"):
166 libcheck =
"#.check_libraries_pseudotarget"
167 check_action = Action(check_libraries,
"*** Checking library dependencies...")
168 env.Command(libcheck,
None, check_action)
169 env.AlwaysBuild(libcheck)
172 if not BUILD_TARGETS:
175 env.Depends(libcheck, [
"lib",
"modules"])
178 env.Depends(libcheck, BUILD_TARGETS)
180 BUILD_TARGETS.append(libcheck)
184 env.AddMethod(run,
"CheckLibraryDependencies")