Add options to run `ldd -u -r` as a "link-what-you-use" tool

Create a LINK_WHAT_YOU_USE target property and corresponding
CMAKE_LINK_WHAT_YOU_USE variable to enable this behavior.
Extend link commands by running `ldd -u -r` to detect shared
libraries that are linked but not needed.
This commit is contained in:
Bill Hoffman 2016-06-02 18:08:30 -04:00 committed by Brad King
parent ba92e11f8b
commit 96242f8022
22 changed files with 164 additions and 6 deletions

View File

@ -217,6 +217,7 @@ Properties on Targets
/prop_tgt/LINK_LIBRARIES
/prop_tgt/LINK_SEARCH_END_STATIC
/prop_tgt/LINK_SEARCH_START_STATIC
/prop_tgt/LINK_WHAT_YOU_USE
/prop_tgt/LOCATION_CONFIG
/prop_tgt/LOCATION
/prop_tgt/MACOSX_BUNDLE_INFO_PLIST

View File

@ -275,6 +275,7 @@ Variables that Control the Build
/variable/CMAKE_LINK_INTERFACE_LIBRARIES
/variable/CMAKE_LINK_LIBRARY_FILE_FLAG
/variable/CMAKE_LINK_LIBRARY_FLAG
/variable/CMAKE_LINK_WHAT_YOU_USE
/variable/CMAKE_MACOSX_BUNDLE
/variable/CMAKE_MACOSX_RPATH
/variable/CMAKE_MAP_IMPORTED_CONFIG_CONFIG

View File

@ -0,0 +1,15 @@
LINK_WHAT_YOU_USE
---------------------------
This is a boolean option that when set to ``TRUE`` will automatically run
``ldd -r -u`` on the target after it is linked. In addition, the linker flag
``-Wl,--no-as-needed`` will be passed to the target with the link command so
that all libraries specified on the command line will be linked into the
target. This will result in the link producing a list of libraries that
provide no symbols used by this target but are being linked to it.
This is only applicable to executable and shared library targets and
will only work when ld and ldd accept the flags used.
This property is initialized by the value of
the :variable:`CMAKE_LINK_WHAT_YOU_USE` variable if it is set
when a target is created.

View File

@ -0,0 +1,6 @@
CMAKE_LINK_WHAT_YOU_USE
---------------------------------
Default value for :prop_tgt:`LINK_WHAT_YOU_USE` target property.
This variable is used to initialize the property on each target as it is
created.

View File

@ -187,6 +187,9 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
this->LocalGenerator->AppendFlags(
linkFlags, this->Makefile->GetDefinition(export_flag_var));
}
if (this->GeneratorTarget->GetProperty("LINK_WHAT_YOU_USE")) {
this->LocalGenerator->AppendFlags(linkFlags, " -Wl,--no-as-needed");
}
// Add language feature flags.
this->AddFeatureFlags(flags, linkLanguage);
@ -356,6 +359,15 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
vars.LinkFlags = linkFlags.c_str();
vars.Manifests = manifests.c_str();
if (this->GeneratorTarget->GetProperty("LINK_WHAT_YOU_USE")) {
std::string cmakeCommand =
this->Convert(cmSystemTools::GetCMakeCommand(), cmLocalGenerator::NONE,
cmLocalGenerator::SHELL);
cmakeCommand += " -E __run_iwyu --lwyu=";
cmakeCommand += targetOutPathReal;
real_link_commands.push_back(cmakeCommand);
}
// Expand placeholders in the commands.
this->LocalGenerator->TargetImplib = targetOutPathImport;
for (std::vector<std::string>::iterator i = real_link_commands.begin();

View File

@ -163,6 +163,9 @@ void cmMakefileLibraryTargetGenerator::WriteSharedLibraryRules(bool relink)
extraFlags, "CMAKE_SHARED_LINKER_FLAGS", this->ConfigName);
this->AddModuleDefinitionFlag(extraFlags);
if (this->GeneratorTarget->GetProperty("LINK_WHAT_YOU_USE")) {
this->LocalGenerator->AppendFlags(extraFlags, " -Wl,--no-as-needed");
}
this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
}
@ -682,6 +685,15 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
// Get the set of commands.
std::string linkRule = this->GetLinkRule(linkRuleVar);
cmSystemTools::ExpandListArgument(linkRule, real_link_commands);
if (this->GeneratorTarget->GetProperty("LINK_WHAT_YOU_USE") &&
(this->GeneratorTarget->GetType() == cmState::SHARED_LIBRARY)) {
std::string cmakeCommand =
this->Convert(cmSystemTools::GetCMakeCommand(),
cmLocalGenerator::NONE, cmLocalGenerator::SHELL);
cmakeCommand += " -E __run_iwyu --lwyu=";
cmakeCommand += targetOutPathReal;
real_link_commands.push_back(cmakeCommand);
}
// Expand placeholders.
for (std::vector<std::string>::iterator i = real_link_commands.begin();
@ -728,6 +740,7 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
commands.insert(commands.end(), commands1.begin(), commands1.end());
commands1.clear();
}
// Add the post-build rules when building but not when relinking.
if (!relink) {
this->LocalGenerator->AppendCustomCommands(

View File

@ -295,6 +295,22 @@ std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeLinkCmd()
const char* linkCmd = mf->GetDefinition(linkCmdVar);
if (linkCmd) {
cmSystemTools::ExpandListArgument(linkCmd, linkCmds);
if (this->GetGeneratorTarget()->GetProperty("LINK_WHAT_YOU_USE")) {
std::string cmakeCommand =
this->GetLocalGenerator()->ConvertToOutputFormat(
cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL);
cmakeCommand += " -E __run_iwyu --lwyu=";
cmGeneratorTarget& gt = *this->GetGeneratorTarget();
const std::string cfgName = this->GetConfigName();
std::string targetOutput = ConvertToNinjaPath(gt.GetFullPath(cfgName));
std::string targetOutputReal =
this->ConvertToNinjaPath(gt.GetFullPath(cfgName,
/*implib=*/false,
/*realpath=*/true));
cmakeCommand += targetOutputReal;
cmakeCommand += " || true";
linkCmds.push_back(cmakeCommand);
}
return linkCmds;
}
}
@ -467,6 +483,10 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
vars["MANIFESTS"] = this->GetManifests();
vars["LINK_PATH"] = frameworkPath + linkPath;
std::string lwyuFlags;
if (genTarget.GetProperty("LINK_WHAT_YOU_USE")) {
lwyuFlags = " -Wl,--no-as-needed";
}
// Compute architecture specific link flags. Yes, these go into a different
// variable for executables, probably due to a mistake made when duplicating
@ -474,16 +494,17 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
if (targetType == cmState::EXECUTABLE) {
std::string t = vars["FLAGS"];
localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
t += lwyuFlags;
vars["FLAGS"] = t;
} else {
std::string t = vars["ARCH_FLAGS"];
localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
vars["ARCH_FLAGS"] = t;
t = "";
t += lwyuFlags;
localGen.AddLanguageFlags(t, TargetLinkLanguage, cfgName);
vars["LANGUAGE_COMPILE_FLAGS"] = t;
}
if (this->GetGeneratorTarget()->HasSOName(cfgName)) {
vars["SONAME_FLAG"] = mf->GetSONameFlag(this->TargetLinkLanguage);
vars["SONAME"] = this->TargetNameSO;
@ -607,7 +628,6 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
vars["POST_BUILD"] = ":";
symlinkVars["POST_BUILD"] = postBuildCmdLine;
}
cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator();
int commandLineLengthLimit = -1;

View File

@ -139,6 +139,7 @@ void cmTarget::SetMakefile(cmMakefile* mf)
this->SetPropertyDefault("C_CLANG_TIDY", 0);
this->SetPropertyDefault("C_COMPILER_LAUNCHER", 0);
this->SetPropertyDefault("C_INCLUDE_WHAT_YOU_USE", 0);
this->SetPropertyDefault("LINK_WHAT_YOU_USE", 0);
this->SetPropertyDefault("C_STANDARD", 0);
this->SetPropertyDefault("C_STANDARD_REQUIRED", 0);
this->SetPropertyDefault("C_EXTENSIONS", 0);

View File

@ -271,6 +271,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
std::string iwyu;
std::string tidy;
std::string sourceFile;
std::string lwyu;
for (std::string::size_type cc = 2; cc < args.size(); cc++) {
std::string const& arg = args[cc];
if (arg == "--") {
@ -281,6 +282,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
tidy = arg.substr(7);
} else if (doing_options && cmHasLiteralPrefix(arg, "--source=")) {
sourceFile = arg.substr(9);
} else if (doing_options && cmHasLiteralPrefix(arg, "--lwyu=")) {
lwyu = arg.substr(7);
} else if (doing_options) {
std::cerr << "__run_iwyu given unknown argument: " << arg << "\n";
return 1;
@ -288,7 +291,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
orig_cmd.push_back(arg);
}
}
if (tidy.empty() && iwyu.empty()) {
if (tidy.empty() && iwyu.empty() && lwyu.empty()) {
std::cerr << "__run_iwyu missing --tidy= or --iwyu=\n";
return 1;
}
@ -296,7 +299,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
std::cerr << "__run_iwyu --tidy= requires --source=\n";
return 1;
}
if (orig_cmd.empty()) {
if (orig_cmd.empty() && lwyu.empty()) {
std::cerr << "__run_iwyu missing compile command after --\n";
return 1;
}
@ -345,13 +348,37 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
std::cerr << "Error running '" << tidy_cmd[0] << "'\n";
return 1;
}
// Output the stdout from clang-tidy to stderr
std::cerr << stdOut;
}
if (!lwyu.empty()) {
// Construct the ldd -r -u (link what you use lwyu) command line
// ldd -u -r lwuy target
std::vector<std::string> lwyu_cmd;
lwyu_cmd.push_back("ldd");
lwyu_cmd.push_back("-u");
lwyu_cmd.push_back("-r");
lwyu_cmd.push_back(lwyu);
// Run the ldd -u -r command line.
// Capture its stdout and hide its stderr.
std::string stdOut;
if (!cmSystemTools::RunSingleCommand(lwyu_cmd, &stdOut, 0, &ret, 0,
cmSystemTools::OUTPUT_NONE)) {
std::cerr << "Error running '" << lwyu_cmd[0] << "'\n";
return 1;
}
// Output the stdout from ldd -r -u to stderr
// Warn if lwyu reported anything.
if (stdOut.find("Unused direct dependencies:") != stdOut.npos) {
std::cerr << "Warning: " << stdOut;
}
}
ret = 0;
// Now run the real compiler command and return its result value.
if (!cmSystemTools::RunSingleCommand(
if (lwyu.empty() &&
!cmSystemTools::RunSingleCommand(
orig_cmd, 0, 0, &ret, 0, cmSystemTools::OUTPUT_PASSTHROUGH)) {
std::cerr << "Error running '" << orig_cmd[0] << "'\n";
return 1;

View File

@ -307,6 +307,14 @@ if(CMAKE_OSX_ARCHITECTURES AND XCODE AND NOT "${XCODE_VERSION}" MATCHES "^[^12]"
endif()
if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
if(UNIX AND NOT CYGWIN)
execute_process(COMMAND ldd --help
OUTPUT_VARIABLE LDD_HELP)
if("${LDD_HELP}" MATCHES
"(-r, --function-relocs.*process data and function relocations.*-u, --unused.*print unused direct dependencies)")
add_RunCMake_test(LinkWhatYouUse)
endif()
endif()
add_executable(pseudo_tidy pseudo_tidy.c)
add_executable(pseudo_iwyu pseudo_iwyu.c)
add_RunCMake_test(ClangTidy -DPSEUDO_TIDY=$<TARGET_FILE:pseudo_tidy>)

View File

@ -0,0 +1,2 @@
.*Warning: Unused direct dependencies.*
.*libm.*

View File

@ -0,0 +1,2 @@
.*Warning: Unused direct dependencies.*
.*libm.*

View File

@ -0,0 +1,3 @@
set(CTEST_USE_LAUNCHERS 1)
include(CTestUseLaunchers)
include(C.cmake)

View File

@ -0,0 +1,4 @@
enable_language(C)
set(CMAKE_LINK_WHAT_YOU_USE TRUE)
add_executable(main main.c)
target_link_libraries(main m)

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.2)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@ -0,0 +1,2 @@
.*Warning: Unused direct dependencies.*
.*libm.*

View File

@ -0,0 +1,2 @@
.*Warning: Unused direct dependencies.*
.*libm.*

View File

@ -0,0 +1,3 @@
set(CTEST_USE_LAUNCHERS 1)
include(CTestUseLaunchers)
include(CXX.cmake)

View File

@ -0,0 +1,4 @@
enable_language(CXX)
set(CMAKE_LINK_WHAT_YOU_USE TRUE)
add_executable(main main.cxx)
target_link_libraries(main m)

View File

@ -0,0 +1,21 @@
include(RunCMake)
function(run_lwyu lang)
# Use a single build tree for tests without cleaning.
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${lang}-build)
set(RunCMake_TEST_NO_CLEAN 1)
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
run_cmake(${lang})
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake_command(${lang}-Build ${CMAKE_COMMAND} --build .)
endfunction()
run_lwyu(CXX)
run_lwyu(C)
if (NOT RunCMake_GENERATOR STREQUAL "Watcom WMake")
run_lwyu(C-launch)
run_lwyu(CXX-launch)
endif()

View File

@ -0,0 +1,4 @@
int main(void)
{
return 0;
}

View File

@ -0,0 +1,4 @@
int main()
{
return 0;
}