7 from SCons.Action
import Action
13 def get_dtneeded(filename):
14 """Use readelf (from binutils) to get a list of required libraries for a
15 given shared object file. This will just look at the NEEDED entries in the
16 dynamic table so in contrast to ldd it will not resolve child
17 dependencies recursively. It will also just return the library names, not in
18 which directory they might be found."""
21 re_readelf = re.compile(
r"^\s*0x0.*\(NEEDED\).*\[(.*)\]\s*$", re.M)
22 readelf_out = subprocess.check_output([
"readelf",
"-d", filename], stderr=subprocess.STDOUT,
23 env=dict(os.environ, LC_ALL=
"C"), encoding=
"utf-8")
24 needed_entries = re_readelf.findall(readelf_out)
26 except Exception
as e:
27 print(
"Could not get dependencies for library %s: %s" % (filename, e))
31 def get_env_list(env, key):
32 """Get a list from the environment by substituting all values and converting
33 them to str. For example to get the list of libraries to be linked in
35 >>> get_env_list(env, "LIBS")
36 ["framework", "framework_dataobjects"]
38 return map(str, env.subst_list(key)[0])
41 def get_package(env, node):
42 """Determine the package of a node by looking at it's sources
43 Hopefully the sources will be inside the build directory so look for the
44 first one there and determine the package name from its name."""
45 builddir = env.Dir(
"$BUILDDIR").get_abspath()
46 for n
in node.sources:
47 fullpath = n.get_abspath()
48 if fullpath.startswith(builddir):
50 components = os.path.relpath(fullpath, builddir).split(os.path.sep)
53 if len(components) == 1:
54 return components[0].split(
"_", 2)[0]
61 def print_libs(title, text, pkg, lib, libs):
62 """Print information on extra/missing libraries"""
63 for l
in sorted(libs):
64 print(
"%s:%s:%s -> %s (%s)" % (title, pkg, lib, l, text))
68 """Check library dependencies. This is called as a builder hence the target and
69 source arguments which are ignored."""
71 libdir = env.Dir(
"$LIBDIR").get_abspath()
73 stack = [env.Dir(
"$LIBDIR"), env.Dir(
"$MODDIR")]
79 for node
in dirnode.all_children():
80 if isinstance(node, Node.FS.Dir):
83 elif node.has_explicit_builder()
and str(node).endswith(
".so"):
87 if os.path.basename(node.get_abspath()) ==
"libdataobjects.so":
90 name = os.path.basename(fullname)
92 if "modules" in fullname.split(os.path.sep):
93 name =
"modules/" + name
95 pkg = get_package(env, node)
97 libraries.append((pkg, name, node))
100 for pkg, lib, node
in sorted(libraries):
102 given = get_env_list(node.get_build_env(),
"$LIBS")
104 needed = get_dtneeded(node.get_abspath())
109 def remove_libprefix(x):
110 """small helper to get rid of lib* prefix for requirements as SCons
111 seems to do this transparently as well"""
112 if x.startswith(
"lib"):
113 print_libs(
"LIB_WARNING",
"dependency given as lib*, please remove "
114 "'lib' prefix in SConscript", pkg, str(node), [x])
119 given = map(remove_libprefix, given)
134 given_internal = set(
"lib%s.so" % l
for l
in given
if os.path.exists(os.path.join(libdir,
"lib%s.so" % l)))
135 needed_internal = set(l
for l
in needed
if os.path.exists(os.path.join(libdir, l)))
139 if GetOption(
"libcheck_extra"):
140 extra = given_internal - needed_internal
141 print_libs(
"LIB_EXTRA",
"dependency not needed and can be removed from SConscript", pkg, lib, extra)
143 if GetOption(
"libcheck_missing"):
144 missing = needed_internal - given_internal
145 print_libs(
"LIB_MISSING",
"library needed directly, please add to SConscript", pkg, lib, missing)
147 print(
"*** finished checking library dependencies")
151 AddOption(
"--check-extra-libraries", dest=
"libcheck_extra", action=
"store_true", default=
False,
152 help=
"if given all libraries will be checked for dependencies in "
153 "the SConscript which are not actually needed")
154 AddOption(
"--check-missing-libraries", dest=
"libcheck_missing", action=
"store_true", default=
False,
155 help=
"if given all libraries will be checked for missing direct dependencies after build")
157 if env.GetOption(
"libcheck_extra")
or env.GetOption(
"libcheck_missing"):
159 libcheck =
"#.check_libraries_pseudotarget"
160 check_action = Action(check_libraries,
"*** Checking library dependencies...")
161 env.Command(libcheck,
None, check_action)
162 env.AlwaysBuild(libcheck)
165 if not BUILD_TARGETS:
168 env.Depends(libcheck, [
"lib",
"modules"])
171 env.Depends(libcheck, BUILD_TARGETS)
173 BUILD_TARGETS.append(libcheck)
177 env.AddMethod(run,
"CheckLibraryDependencies")