Browse Source

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.
Bill Hoffman 9 years ago
parent
commit
96242f8022

+ 1 - 0
Help/manual/cmake-properties.7.rst

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

+ 1 - 0
Help/manual/cmake-variables.7.rst

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

+ 15 - 0
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.

+ 6 - 0
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.

+ 12 - 0
Source/cmMakefileExecutableTargetGenerator.cxx

@@ -187,6 +187,9 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
     this->LocalGenerator->AppendFlags(
     this->LocalGenerator->AppendFlags(
       linkFlags, this->Makefile->GetDefinition(export_flag_var));
       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.
   // Add language feature flags.
   this->AddFeatureFlags(flags, linkLanguage);
   this->AddFeatureFlags(flags, linkLanguage);
@@ -356,6 +359,15 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
     vars.LinkFlags = linkFlags.c_str();
     vars.LinkFlags = linkFlags.c_str();
     vars.Manifests = manifests.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.
     // Expand placeholders in the commands.
     this->LocalGenerator->TargetImplib = targetOutPathImport;
     this->LocalGenerator->TargetImplib = targetOutPathImport;
     for (std::vector<std::string>::iterator i = real_link_commands.begin();
     for (std::vector<std::string>::iterator i = real_link_commands.begin();

+ 13 - 0
Source/cmMakefileLibraryTargetGenerator.cxx

@@ -163,6 +163,9 @@ void cmMakefileLibraryTargetGenerator::WriteSharedLibraryRules(bool relink)
     extraFlags, "CMAKE_SHARED_LINKER_FLAGS", this->ConfigName);
     extraFlags, "CMAKE_SHARED_LINKER_FLAGS", this->ConfigName);
   this->AddModuleDefinitionFlag(extraFlags);
   this->AddModuleDefinitionFlag(extraFlags);
 
 
+  if (this->GeneratorTarget->GetProperty("LINK_WHAT_YOU_USE")) {
+    this->LocalGenerator->AppendFlags(extraFlags, " -Wl,--no-as-needed");
+  }
   this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
   this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
 }
 }
 
 
@@ -682,6 +685,15 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
       // Get the set of commands.
       // Get the set of commands.
       std::string linkRule = this->GetLinkRule(linkRuleVar);
       std::string linkRule = this->GetLinkRule(linkRuleVar);
       cmSystemTools::ExpandListArgument(linkRule, real_link_commands);
       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.
       // Expand placeholders.
       for (std::vector<std::string>::iterator i = real_link_commands.begin();
       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());
     commands.insert(commands.end(), commands1.begin(), commands1.end());
     commands1.clear();
     commands1.clear();
   }
   }
+
   // Add the post-build rules when building but not when relinking.
   // Add the post-build rules when building but not when relinking.
   if (!relink) {
   if (!relink) {
     this->LocalGenerator->AppendCustomCommands(
     this->LocalGenerator->AppendCustomCommands(

+ 22 - 2
Source/cmNinjaNormalTargetGenerator.cxx

@@ -295,6 +295,22 @@ std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeLinkCmd()
     const char* linkCmd = mf->GetDefinition(linkCmdVar);
     const char* linkCmd = mf->GetDefinition(linkCmdVar);
     if (linkCmd) {
     if (linkCmd) {
       cmSystemTools::ExpandListArgument(linkCmd, linkCmds);
       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;
       return linkCmds;
     }
     }
   }
   }
@@ -467,6 +483,10 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
   vars["MANIFESTS"] = this->GetManifests();
   vars["MANIFESTS"] = this->GetManifests();
 
 
   vars["LINK_PATH"] = frameworkPath + linkPath;
   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
   // Compute architecture specific link flags.  Yes, these go into a different
   // variable for executables, probably due to a mistake made when duplicating
   // variable for executables, probably due to a mistake made when duplicating
@@ -474,16 +494,17 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
   if (targetType == cmState::EXECUTABLE) {
   if (targetType == cmState::EXECUTABLE) {
     std::string t = vars["FLAGS"];
     std::string t = vars["FLAGS"];
     localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
     localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
+    t += lwyuFlags;
     vars["FLAGS"] = t;
     vars["FLAGS"] = t;
   } else {
   } else {
     std::string t = vars["ARCH_FLAGS"];
     std::string t = vars["ARCH_FLAGS"];
     localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
     localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
     vars["ARCH_FLAGS"] = t;
     vars["ARCH_FLAGS"] = t;
     t = "";
     t = "";
+    t += lwyuFlags;
     localGen.AddLanguageFlags(t, TargetLinkLanguage, cfgName);
     localGen.AddLanguageFlags(t, TargetLinkLanguage, cfgName);
     vars["LANGUAGE_COMPILE_FLAGS"] = t;
     vars["LANGUAGE_COMPILE_FLAGS"] = t;
   }
   }
-
   if (this->GetGeneratorTarget()->HasSOName(cfgName)) {
   if (this->GetGeneratorTarget()->HasSOName(cfgName)) {
     vars["SONAME_FLAG"] = mf->GetSONameFlag(this->TargetLinkLanguage);
     vars["SONAME_FLAG"] = mf->GetSONameFlag(this->TargetLinkLanguage);
     vars["SONAME"] = this->TargetNameSO;
     vars["SONAME"] = this->TargetNameSO;
@@ -607,7 +628,6 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
     vars["POST_BUILD"] = ":";
     vars["POST_BUILD"] = ":";
     symlinkVars["POST_BUILD"] = postBuildCmdLine;
     symlinkVars["POST_BUILD"] = postBuildCmdLine;
   }
   }
-
   cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator();
   cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator();
 
 
   int commandLineLengthLimit = -1;
   int commandLineLengthLimit = -1;

+ 1 - 0
Source/cmTarget.cxx

@@ -139,6 +139,7 @@ void cmTarget::SetMakefile(cmMakefile* mf)
     this->SetPropertyDefault("C_CLANG_TIDY", 0);
     this->SetPropertyDefault("C_CLANG_TIDY", 0);
     this->SetPropertyDefault("C_COMPILER_LAUNCHER", 0);
     this->SetPropertyDefault("C_COMPILER_LAUNCHER", 0);
     this->SetPropertyDefault("C_INCLUDE_WHAT_YOU_USE", 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", 0);
     this->SetPropertyDefault("C_STANDARD_REQUIRED", 0);
     this->SetPropertyDefault("C_STANDARD_REQUIRED", 0);
     this->SetPropertyDefault("C_EXTENSIONS", 0);
     this->SetPropertyDefault("C_EXTENSIONS", 0);

+ 31 - 4
Source/cmcmd.cxx

@@ -271,6 +271,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
       std::string iwyu;
       std::string iwyu;
       std::string tidy;
       std::string tidy;
       std::string sourceFile;
       std::string sourceFile;
+      std::string lwyu;
       for (std::string::size_type cc = 2; cc < args.size(); cc++) {
       for (std::string::size_type cc = 2; cc < args.size(); cc++) {
         std::string const& arg = args[cc];
         std::string const& arg = args[cc];
         if (arg == "--") {
         if (arg == "--") {
@@ -281,6 +282,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
           tidy = arg.substr(7);
           tidy = arg.substr(7);
         } else if (doing_options && cmHasLiteralPrefix(arg, "--source=")) {
         } else if (doing_options && cmHasLiteralPrefix(arg, "--source=")) {
           sourceFile = arg.substr(9);
           sourceFile = arg.substr(9);
+        } else if (doing_options && cmHasLiteralPrefix(arg, "--lwyu=")) {
+          lwyu = arg.substr(7);
         } else if (doing_options) {
         } else if (doing_options) {
           std::cerr << "__run_iwyu given unknown argument: " << arg << "\n";
           std::cerr << "__run_iwyu given unknown argument: " << arg << "\n";
           return 1;
           return 1;
@@ -288,7 +291,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
           orig_cmd.push_back(arg);
           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";
         std::cerr << "__run_iwyu missing --tidy= or --iwyu=\n";
         return 1;
         return 1;
       }
       }
@@ -296,7 +299,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
         std::cerr << "__run_iwyu --tidy= requires --source=\n";
         std::cerr << "__run_iwyu --tidy= requires --source=\n";
         return 1;
         return 1;
       }
       }
-      if (orig_cmd.empty()) {
+      if (orig_cmd.empty() && lwyu.empty()) {
         std::cerr << "__run_iwyu missing compile command after --\n";
         std::cerr << "__run_iwyu missing compile command after --\n";
         return 1;
         return 1;
       }
       }
@@ -345,13 +348,37 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
           std::cerr << "Error running '" << tidy_cmd[0] << "'\n";
           std::cerr << "Error running '" << tidy_cmd[0] << "'\n";
           return 1;
           return 1;
         }
         }
-
         // Output the stdout from clang-tidy to stderr
         // Output the stdout from clang-tidy to stderr
         std::cerr << stdOut;
         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.
       // 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)) {
             orig_cmd, 0, 0, &ret, 0, cmSystemTools::OUTPUT_PASSTHROUGH)) {
         std::cerr << "Error running '" << orig_cmd[0] << "'\n";
         std::cerr << "Error running '" << orig_cmd[0] << "'\n";
         return 1;
         return 1;

+ 8 - 0
Tests/RunCMake/CMakeLists.txt

@@ -307,6 +307,14 @@ if(CMAKE_OSX_ARCHITECTURES AND XCODE AND NOT "${XCODE_VERSION}" MATCHES "^[^12]"
 endif()
 endif()
 
 
 if("${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
 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_tidy pseudo_tidy.c)
   add_executable(pseudo_iwyu pseudo_iwyu.c)
   add_executable(pseudo_iwyu pseudo_iwyu.c)
   add_RunCMake_test(ClangTidy -DPSEUDO_TIDY=$<TARGET_FILE:pseudo_tidy>)
   add_RunCMake_test(ClangTidy -DPSEUDO_TIDY=$<TARGET_FILE:pseudo_tidy>)

+ 2 - 0
Tests/RunCMake/LinkWhatYouUse/C-Build-stdout.txt

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

+ 2 - 0
Tests/RunCMake/LinkWhatYouUse/C-launch-Build-stdout.txt

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

+ 3 - 0
Tests/RunCMake/LinkWhatYouUse/C-launch.cmake

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

+ 4 - 0
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)

+ 3 - 0
Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt

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

+ 2 - 0
Tests/RunCMake/LinkWhatYouUse/CXX-Build-stdout.txt

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

+ 2 - 0
Tests/RunCMake/LinkWhatYouUse/CXX-launch-Build-stdout.txt

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

+ 3 - 0
Tests/RunCMake/LinkWhatYouUse/CXX-launch.cmake

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

+ 4 - 0
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)

+ 21 - 0
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()

+ 4 - 0
Tests/RunCMake/LinkWhatYouUse/main.c

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

+ 4 - 0
Tests/RunCMake/LinkWhatYouUse/main.cxx

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