13 from SCons.Script
import AddOption, GetOption, BUILD_TARGETS
14 from SCons
import Node
15 from SCons.Action
import Action
20 def get_dtneeded(filename):
21 """Use readelf (from binutils) to get a list of required libraries for a
22 given shared object file. This will just look at the NEEDED entries in the
23 dynamic table so in contrast to ldd it will not resolve child
24 dependencies recursively. It will also just return the library names, not in
25 which directory they might be found."""
28 re_readelf = re.compile(
r"^\s*0x0.*\(NEEDED\).*\[(.*)\]\s*$", re.M)
29 readelf_out = subprocess.check_output([
"readelf",
"-d", filename], stderr=subprocess.STDOUT,
30 env=dict(os.environ, LC_ALL=
"C"), encoding=
"utf-8")
31 needed_entries = re_readelf.findall(readelf_out)
33 except Exception
as e:
34 print(
"Could not get dependencies for library %s: %s" % (filename, e))
38 def get_env_list(env, key):
39 """Get a list from the environment by substituting all values and converting
40 them to str. For example to get the list of libraries to be linked in
42 >>> get_env_list(env, "LIBS")
43 ["framework", "framework_dataobjects"]
45 return map(str, env.subst_list(key)[0])
48 def get_package(env, node):
49 """Determine the package of a node by looking at it's sources
50 Hopefully the sources will be inside the build directory so look for the
51 first one there and determine the package name from its name."""
52 builddir = env.Dir(
"$BUILDDIR").get_abspath()
53 for n
in node.sources:
54 fullpath = n.get_abspath()
55 if fullpath.startswith(builddir):
57 components = os.path.relpath(fullpath, builddir).split(os.path.sep)
60 if len(components) == 1:
61 return components[0].split(
"_", 2)[0]
68 def print_libs(title, text, pkg, lib, libs):
69 """Print information on extra/missing libraries"""
70 for library
in sorted(libs):
71 print(
"%s:%s:%s -> %s (%s)" % (title, pkg, lib, library, text))
75 """Check library dependencies. This is called as a builder hence the target and
76 source arguments which are ignored."""
78 libdir = env.Dir(
"$LIBDIR").get_abspath()
80 stack = [env.Dir(
"$LIBDIR"), env.Dir(
"$MODDIR")]
86 for node
in dirnode.all_children():
87 if isinstance(node, Node.FS.Dir):
90 elif node.has_explicit_builder()
and str(node).endswith(
".so"):
94 if os.path.basename(node.get_abspath()) ==
"libdataobjects.so":
97 name = os.path.basename(fullname)
99 if "modules" in fullname.split(os.path.sep):
100 name =
"modules/" + name
102 pkg = get_package(env, node)
104 libraries.append((pkg, name, node))
107 for pkg, lib, node
in sorted(libraries):
109 given = get_env_list(node.get_build_env(),
"$LIBS")
111 needed = get_dtneeded(node.get_abspath())
116 def remove_libprefix(x):
117 """small helper to get rid of lib* prefix for requirements as SCons
118 seems to do this transparently as well"""
119 if x.startswith(
"lib"):
120 print_libs(
"LIB_WARNING",
"dependency given as lib*, please remove "
121 "'lib' prefix in SConscript", pkg, str(node), [x])
126 given = map(remove_libprefix, given)
141 given_internal = set(
"lib%s.so" % library
for library
in given
if os.path.exists(os.path.join(libdir,
142 "lib%s.so" % library)))
143 needed_internal = set(library
for library
in needed
if os.path.exists(os.path.join(libdir, library)))
147 if GetOption(
"libcheck_extra"):
148 extra = given_internal - needed_internal
149 print_libs(
"LIB_EXTRA",
"dependency not needed and can be removed from SConscript", pkg, lib, extra)
151 if GetOption(
"libcheck_missing"):
152 missing = needed_internal - given_internal
153 print_libs(
"LIB_MISSING",
"library needed directly, please add to SConscript", pkg, lib, missing)
155 print(
"*** finished checking library dependencies")
159 AddOption(
"--check-extra-libraries", dest=
"libcheck_extra", action=
"store_true", default=
False,
160 help=
"if given all libraries will be checked for dependencies in "
161 "the SConscript which are not actually needed")
162 AddOption(
"--check-missing-libraries", dest=
"libcheck_missing", action=
"store_true", default=
False,
163 help=
"if given all libraries will be checked for missing direct dependencies after build")
165 if env.GetOption(
"libcheck_extra")
or env.GetOption(
"libcheck_missing"):
167 libcheck =
"#.check_libraries_pseudotarget"
168 check_action = Action(check_libraries,
"*** Checking library dependencies...")
169 env.Command(libcheck,
None, check_action)
170 env.AlwaysBuild(libcheck)
173 if not BUILD_TARGETS:
176 env.Depends(libcheck, [
"lib",
"modules"])
179 env.Depends(libcheck, BUILD_TARGETS)
181 BUILD_TARGETS.append(libcheck)
185 env.AddMethod(run,
"CheckLibraryDependencies")