diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 3574b7fc5..d16f2316a 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -218,6 +218,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 diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 85b8eae71..36d00dc7d 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -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 diff --git a/Help/prop_tgt/LINK_WHAT_YOU_USE.rst b/Help/prop_tgt/LINK_WHAT_YOU_USE.rst new file mode 100644 index 000000000..32d6edb07 --- /dev/null +++ b/Help/prop_tgt/LINK_WHAT_YOU_USE.rst @@ -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. diff --git a/Help/release/dev/link_what_you_use.rst b/Help/release/dev/link_what_you_use.rst new file mode 100644 index 000000000..8d1e59871 --- /dev/null +++ b/Help/release/dev/link_what_you_use.rst @@ -0,0 +1,7 @@ +link_what_you_use +----------------- + +* A :prop_tgt:`LINK_WHAT_YOU_USE` target property and supporting + :variable:`CMAKE_LINK_WHAT_YOU_USE` variable were introduced + to detect (on UNIX) shared libraries that are linked but not + needed by running ``ldd -r -u``. diff --git a/Help/variable/CMAKE_LINK_WHAT_YOU_USE.rst b/Help/variable/CMAKE_LINK_WHAT_YOU_USE.rst new file mode 100644 index 000000000..90c4d3ff2 --- /dev/null +++ b/Help/variable/CMAKE_LINK_WHAT_YOU_USE.rst @@ -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. diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx index 9d422578b..ba4c6e6b5 100644 --- a/Source/cmMakefileExecutableTargetGenerator.cxx +++ b/Source/cmMakefileExecutableTargetGenerator.cxx @@ -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::iterator i = real_link_commands.begin(); diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index 438f90f3d..5348b8089 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -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); } @@ -686,6 +689,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::iterator i = real_link_commands.begin(); @@ -732,6 +744,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( diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 5225e3ff0..04ba842d9 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -313,6 +313,22 @@ std::vector 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; } } @@ -485,6 +501,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 @@ -492,16 +512,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; @@ -625,7 +646,6 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() vars["POST_BUILD"] = ":"; symlinkVars["POST_BUILD"] = postBuildCmdLine; } - cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator(); int commandLineLengthLimit = -1; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 55da611f8..43860802c 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -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); diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 535dead22..161256e25 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -271,6 +271,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector& 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& 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& 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& 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& 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 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; diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index d16e5e7c6..b6d1c3857 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -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=$) diff --git a/Tests/RunCMake/LinkWhatYouUse/C-Build-stdout.txt b/Tests/RunCMake/LinkWhatYouUse/C-Build-stdout.txt new file mode 100644 index 000000000..95eafadb0 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/C-Build-stdout.txt @@ -0,0 +1,2 @@ +.*Warning: Unused direct dependencies.* +.*libm.* diff --git a/Tests/RunCMake/LinkWhatYouUse/C-launch-Build-stdout.txt b/Tests/RunCMake/LinkWhatYouUse/C-launch-Build-stdout.txt new file mode 100644 index 000000000..95eafadb0 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/C-launch-Build-stdout.txt @@ -0,0 +1,2 @@ +.*Warning: Unused direct dependencies.* +.*libm.* diff --git a/Tests/RunCMake/LinkWhatYouUse/C-launch.cmake b/Tests/RunCMake/LinkWhatYouUse/C-launch.cmake new file mode 100644 index 000000000..e66ca203e --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/C-launch.cmake @@ -0,0 +1,3 @@ +set(CTEST_USE_LAUNCHERS 1) +include(CTestUseLaunchers) +include(C.cmake) diff --git a/Tests/RunCMake/LinkWhatYouUse/C.cmake b/Tests/RunCMake/LinkWhatYouUse/C.cmake new file mode 100644 index 000000000..4c3f42828 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/C.cmake @@ -0,0 +1,4 @@ +enable_language(C) +set(CMAKE_LINK_WHAT_YOU_USE TRUE) +add_executable(main main.c) +target_link_libraries(main m) diff --git a/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt b/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt new file mode 100644 index 000000000..18dfd2686 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.2) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/LinkWhatYouUse/CXX-Build-stdout.txt b/Tests/RunCMake/LinkWhatYouUse/CXX-Build-stdout.txt new file mode 100644 index 000000000..95eafadb0 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/CXX-Build-stdout.txt @@ -0,0 +1,2 @@ +.*Warning: Unused direct dependencies.* +.*libm.* diff --git a/Tests/RunCMake/LinkWhatYouUse/CXX-launch-Build-stdout.txt b/Tests/RunCMake/LinkWhatYouUse/CXX-launch-Build-stdout.txt new file mode 100644 index 000000000..95eafadb0 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/CXX-launch-Build-stdout.txt @@ -0,0 +1,2 @@ +.*Warning: Unused direct dependencies.* +.*libm.* diff --git a/Tests/RunCMake/LinkWhatYouUse/CXX-launch.cmake b/Tests/RunCMake/LinkWhatYouUse/CXX-launch.cmake new file mode 100644 index 000000000..3002c9d68 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/CXX-launch.cmake @@ -0,0 +1,3 @@ +set(CTEST_USE_LAUNCHERS 1) +include(CTestUseLaunchers) +include(CXX.cmake) diff --git a/Tests/RunCMake/LinkWhatYouUse/CXX.cmake b/Tests/RunCMake/LinkWhatYouUse/CXX.cmake new file mode 100644 index 000000000..95558327e --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/CXX.cmake @@ -0,0 +1,4 @@ +enable_language(CXX) +set(CMAKE_LINK_WHAT_YOU_USE TRUE) +add_executable(main main.cxx) +target_link_libraries(main m) diff --git a/Tests/RunCMake/LinkWhatYouUse/RunCMakeTest.cmake b/Tests/RunCMake/LinkWhatYouUse/RunCMakeTest.cmake new file mode 100644 index 000000000..897f56817 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/RunCMakeTest.cmake @@ -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() diff --git a/Tests/RunCMake/LinkWhatYouUse/main.c b/Tests/RunCMake/LinkWhatYouUse/main.c new file mode 100644 index 000000000..8488f4e58 --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +} diff --git a/Tests/RunCMake/LinkWhatYouUse/main.cxx b/Tests/RunCMake/LinkWhatYouUse/main.cxx new file mode 100644 index 000000000..f8b643afb --- /dev/null +++ b/Tests/RunCMake/LinkWhatYouUse/main.cxx @@ -0,0 +1,4 @@ +int main() +{ + return 0; +}