1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 
4 #include "cmRuntimeDependencyArchive.h"
5 
6 #include "cmBinUtilsLinuxELFLinker.h"
7 #include "cmBinUtilsMacOSMachOLinker.h"
8 #include "cmBinUtilsWindowsPELinker.h"
9 #include "cmExecutionStatus.h"
10 #include "cmMakefile.h"
11 #include "cmStateTypes.h"
12 #include "cmStringAlgorithms.h"
13 #include "cmSystemTools.h"
14 
15 #if defined(_WIN32)
16 #  include "cmGlobalGenerator.h"
17 #  ifndef CMAKE_BOOTSTRAP
18 #    include "cmGlobalVisualStudioVersionedGenerator.h"
19 #  endif
20 #  include "cmsys/Glob.hxx"
21 
22 #  include "cmVSSetupHelper.h"
23 #endif
24 
25 #include <algorithm>
26 #include <sstream>
27 #include <string>
28 #include <utility>
29 #include <vector>
30 
31 #include <cm/memory>
32 
33 #if defined(_WIN32)
AddVisualStudioPath(std::vector<std::string> & paths,const std::string & prefix,unsigned int version,cmGlobalGenerator * gg)34 static void AddVisualStudioPath(std::vector<std::string>& paths,
35                                 const std::string& prefix,
36                                 unsigned int version, cmGlobalGenerator* gg)
37 {
38   // If generating for the VS IDE, use the same instance.
39   std::string vsloc;
40   bool found = false;
41 #  ifndef CMAKE_BOOTSTRAP
42   if (cmHasPrefix(gg->GetName(), prefix)) {
43     cmGlobalVisualStudioVersionedGenerator* vsgen =
44       static_cast<cmGlobalVisualStudioVersionedGenerator*>(gg);
45     if (vsgen->GetVSInstance(vsloc)) {
46       found = true;
47     }
48   }
49 #  endif
50 
51   // Otherwise, find a VS instance ourselves.
52   if (!found) {
53     cmVSSetupAPIHelper vsSetupAPIHelper(version);
54     if (vsSetupAPIHelper.GetVSInstanceInfo(vsloc)) {
55       cmSystemTools::ConvertToUnixSlashes(vsloc);
56       found = true;
57     }
58   }
59 
60   if (found) {
61     cmsys::Glob glob;
62     glob.SetListDirs(true);
63     glob.FindFiles(vsloc + "/VC/Tools/MSVC/*");
64     for (auto const& vcdir : glob.GetFiles()) {
65       paths.push_back(vcdir + "/bin/Hostx64/x64");
66       paths.push_back(vcdir + "/bin/Hostx86/x64");
67       paths.push_back(vcdir + "/bin/Hostx64/x86");
68       paths.push_back(vcdir + "/bin/Hostx86/x86");
69     }
70   }
71 }
72 
AddRegistryPath(std::vector<std::string> & paths,const std::string & path,cmMakefile * mf)73 static void AddRegistryPath(std::vector<std::string>& paths,
74                             const std::string& path, cmMakefile* mf)
75 {
76   // We should view the registry as the target application would view
77   // it.
78   cmSystemTools::KeyWOW64 view = cmSystemTools::KeyWOW64_32;
79   cmSystemTools::KeyWOW64 other_view = cmSystemTools::KeyWOW64_64;
80   if (mf->PlatformIs64Bit()) {
81     view = cmSystemTools::KeyWOW64_64;
82     other_view = cmSystemTools::KeyWOW64_32;
83   }
84 
85   // Expand using the view of the target application.
86   std::string expanded = path;
87   cmSystemTools::ExpandRegistryValues(expanded, view);
88   cmSystemTools::GlobDirs(expanded, paths);
89 
90   // Executables can be either 32-bit or 64-bit, so expand using the
91   // alternative view.
92   expanded = path;
93   cmSystemTools::ExpandRegistryValues(expanded, other_view);
94   cmSystemTools::GlobDirs(expanded, paths);
95 }
96 
AddEnvPath(std::vector<std::string> & paths,const std::string & var,const std::string & suffix)97 static void AddEnvPath(std::vector<std::string>& paths, const std::string& var,
98                        const std::string& suffix)
99 {
100   std::string value;
101   if (cmSystemTools::GetEnv(var, value)) {
102     paths.push_back(value + suffix);
103   }
104 }
105 #endif
106 
TransformCompile(const std::string & str)107 static cmsys::RegularExpression TransformCompile(const std::string& str)
108 {
109   return cmsys::RegularExpression(str);
110 }
111 
cmRuntimeDependencyArchive(cmExecutionStatus & status,std::vector<std::string> searchDirectories,std::string bundleExecutable,const std::vector<std::string> & preIncludeRegexes,const std::vector<std::string> & preExcludeRegexes,const std::vector<std::string> & postIncludeRegexes,const std::vector<std::string> & postExcludeRegexes,std::vector<std::string> postIncludeFiles,std::vector<std::string> postExcludeFiles,std::vector<std::string> postExcludeFilesStrict)112 cmRuntimeDependencyArchive::cmRuntimeDependencyArchive(
113   cmExecutionStatus& status, std::vector<std::string> searchDirectories,
114   std::string bundleExecutable,
115   const std::vector<std::string>& preIncludeRegexes,
116   const std::vector<std::string>& preExcludeRegexes,
117   const std::vector<std::string>& postIncludeRegexes,
118   const std::vector<std::string>& postExcludeRegexes,
119   std::vector<std::string> postIncludeFiles,
120   std::vector<std::string> postExcludeFiles,
121   std::vector<std::string> postExcludeFilesStrict)
122   : Status(status)
123   , SearchDirectories(std::move(searchDirectories))
124   , BundleExecutable(std::move(bundleExecutable))
125   , PreIncludeRegexes(preIncludeRegexes.size())
126   , PreExcludeRegexes(preExcludeRegexes.size())
127   , PostIncludeRegexes(postIncludeRegexes.size())
128   , PostExcludeRegexes(postExcludeRegexes.size())
129   , PostIncludeFiles(std::move(postIncludeFiles))
130   , PostExcludeFiles(std::move(postExcludeFiles))
131   , PostExcludeFilesStrict(std::move(postExcludeFilesStrict))
132 {
133   std::transform(preIncludeRegexes.begin(), preIncludeRegexes.end(),
134                  this->PreIncludeRegexes.begin(), TransformCompile);
135   std::transform(preExcludeRegexes.begin(), preExcludeRegexes.end(),
136                  this->PreExcludeRegexes.begin(), TransformCompile);
137   std::transform(postIncludeRegexes.begin(), postIncludeRegexes.end(),
138                  this->PostIncludeRegexes.begin(), TransformCompile);
139   std::transform(postExcludeRegexes.begin(), postExcludeRegexes.end(),
140                  this->PostExcludeRegexes.begin(), TransformCompile);
141 }
142 
Prepare()143 bool cmRuntimeDependencyArchive::Prepare()
144 {
145   std::string platform = this->GetMakefile()->GetSafeDefinition(
146     "CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM");
147   if (platform.empty()) {
148     std::string systemName =
149       this->GetMakefile()->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
150     if (systemName == "Windows") {
151       platform = "windows+pe";
152     } else if (systemName == "Darwin") {
153       platform = "macos+macho";
154     } else if (systemName == "Linux") {
155       platform = "linux+elf";
156     }
157   }
158   if (platform == "linux+elf") {
159     this->Linker = cm::make_unique<cmBinUtilsLinuxELFLinker>(this);
160   } else if (platform == "windows+pe") {
161     this->Linker = cm::make_unique<cmBinUtilsWindowsPELinker>(this);
162   } else if (platform == "macos+macho") {
163     this->Linker = cm::make_unique<cmBinUtilsMacOSMachOLinker>(this);
164   } else {
165     std::ostringstream e;
166     e << "Invalid value for CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM: "
167       << platform;
168     this->SetError(e.str());
169     return false;
170   }
171 
172   return this->Linker->Prepare();
173 }
174 
GetRuntimeDependencies(const std::vector<std::string> & executables,const std::vector<std::string> & libraries,const std::vector<std::string> & modules)175 bool cmRuntimeDependencyArchive::GetRuntimeDependencies(
176   const std::vector<std::string>& executables,
177   const std::vector<std::string>& libraries,
178   const std::vector<std::string>& modules)
179 {
180   for (auto const& exe : executables) {
181     if (!this->Linker->ScanDependencies(exe, cmStateEnums::EXECUTABLE)) {
182       return false;
183     }
184   }
185   for (auto const& lib : libraries) {
186     if (!this->Linker->ScanDependencies(lib, cmStateEnums::SHARED_LIBRARY)) {
187       return false;
188     }
189   }
190   return std::all_of(
191     modules.begin(), modules.end(), [this](std::string const& mod) -> bool {
192       return this->Linker->ScanDependencies(mod, cmStateEnums::MODULE_LIBRARY);
193     });
194 }
195 
SetError(const std::string & e)196 void cmRuntimeDependencyArchive::SetError(const std::string& e)
197 {
198   this->Status.SetError(e);
199 }
200 
GetBundleExecutable() const201 const std::string& cmRuntimeDependencyArchive::GetBundleExecutable() const
202 {
203   return this->BundleExecutable;
204 }
205 
206 const std::vector<std::string>&
GetSearchDirectories() const207 cmRuntimeDependencyArchive::GetSearchDirectories() const
208 {
209   return this->SearchDirectories;
210 }
211 
GetGetRuntimeDependenciesTool() const212 const std::string& cmRuntimeDependencyArchive::GetGetRuntimeDependenciesTool()
213   const
214 {
215   return this->GetMakefile()->GetSafeDefinition(
216     "CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL");
217 }
218 
GetGetRuntimeDependenciesCommand(const std::string & search,std::vector<std::string> & command) const219 bool cmRuntimeDependencyArchive::GetGetRuntimeDependenciesCommand(
220   const std::string& search, std::vector<std::string>& command) const
221 {
222   // First see if it was supplied by the user
223   std::string toolCommand = this->GetMakefile()->GetSafeDefinition(
224     "CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND");
225   if (toolCommand.empty() && search == "objdump") {
226     toolCommand = this->GetMakefile()->GetSafeDefinition("CMAKE_OBJDUMP");
227   }
228   if (!toolCommand.empty()) {
229     cmExpandList(toolCommand, command);
230     return true;
231   }
232 
233   // Now go searching for it
234   std::vector<std::string> paths;
235 #ifdef _WIN32
236   cmGlobalGenerator* gg = this->GetMakefile()->GetGlobalGenerator();
237 
238   // Add newer Visual Studio paths
239   AddVisualStudioPath(paths, "Visual Studio 16 ", 16, gg);
240   AddVisualStudioPath(paths, "Visual Studio 15 ", 15, gg);
241 
242   // Add older Visual Studio paths
243   AddRegistryPath(
244     paths,
245     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\14.0;InstallDir]/"
246     "../../VC/bin",
247     this->GetMakefile());
248   AddEnvPath(paths, "VS140COMNTOOLS", "/../../VC/bin");
249   paths.push_back(
250     "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin");
251   AddRegistryPath(
252     paths,
253     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\12.0;InstallDir]/"
254     "../../VC/bin",
255     this->GetMakefile());
256   AddEnvPath(paths, "VS120COMNTOOLS", "/../../VC/bin");
257   paths.push_back(
258     "C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/bin");
259   AddRegistryPath(
260     paths,
261     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\11.0;InstallDir]/"
262     "../../VC/bin",
263     this->GetMakefile());
264   AddEnvPath(paths, "VS110COMNTOOLS", "/../../VC/bin");
265   paths.push_back(
266     "C:/Program Files (x86)/Microsoft Visual Studio 11.0/VC/bin");
267   AddRegistryPath(
268     paths,
269     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0;InstallDir]/"
270     "../../VC/bin",
271     this->GetMakefile());
272   AddEnvPath(paths, "VS100COMNTOOLS", "/../../VC/bin");
273   paths.push_back(
274     "C:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/bin");
275   AddRegistryPath(
276     paths,
277     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\9.0;InstallDir]/"
278     "../../VC/bin",
279     this->GetMakefile());
280   AddEnvPath(paths, "VS90COMNTOOLS", "/../../VC/bin");
281   paths.push_back("C:/Program Files/Microsoft Visual Studio 9.0/VC/bin");
282   paths.push_back("C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin");
283   AddRegistryPath(
284     paths,
285     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\8.0;InstallDir]/"
286     "../../VC/bin",
287     this->GetMakefile());
288   AddEnvPath(paths, "VS80COMNTOOLS", "/../../VC/bin");
289   paths.push_back("C:/Program Files/Microsoft Visual Studio 8/VC/BIN");
290   paths.push_back("C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN");
291   AddRegistryPath(
292     paths,
293     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\7.1;InstallDir]/"
294     "../../VC7/bin",
295     this->GetMakefile());
296   AddEnvPath(paths, "VS71COMNTOOLS", "/../../VC7/bin");
297   paths.push_back(
298     "C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN");
299   paths.push_back(
300     "C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN");
301 #endif
302 
303   std::string program = cmSystemTools::FindProgram(search, paths);
304   if (!program.empty()) {
305     command = { program };
306     return true;
307   }
308 
309   // Couldn't find it
310   return false;
311 }
312 
IsPreExcluded(const std::string & name) const313 bool cmRuntimeDependencyArchive::IsPreExcluded(const std::string& name) const
314 {
315   cmsys::RegularExpressionMatch match;
316   auto const regexMatch =
317     [&match, name](const cmsys::RegularExpression& regex) -> bool {
318     return regex.find(name.c_str(), match);
319   };
320   auto const regexSearch =
321     [&regexMatch](
322       const std::vector<cmsys::RegularExpression>& regexes) -> bool {
323     return std::any_of(regexes.begin(), regexes.end(), regexMatch);
324   };
325 
326   return !regexSearch(this->PreIncludeRegexes) &&
327     regexSearch(this->PreExcludeRegexes);
328 }
329 
IsPostExcluded(const std::string & name) const330 bool cmRuntimeDependencyArchive::IsPostExcluded(const std::string& name) const
331 {
332   cmsys::RegularExpressionMatch match;
333   auto const regexMatch =
334     [&match, name](const cmsys::RegularExpression& regex) -> bool {
335     return regex.find(name.c_str(), match);
336   };
337   auto const regexSearch =
338     [&regexMatch](
339       const std::vector<cmsys::RegularExpression>& regexes) -> bool {
340     return std::any_of(regexes.begin(), regexes.end(), regexMatch);
341   };
342   auto const fileMatch = [name](const std::string& file) -> bool {
343     return cmSystemTools::SameFile(file, name);
344   };
345   auto const fileSearch =
346     [&fileMatch](const std::vector<std::string>& files) -> bool {
347     return std::any_of(files.begin(), files.end(), fileMatch);
348   };
349 
350   return fileSearch(this->PostExcludeFilesStrict) ||
351     (!(regexSearch(this->PostIncludeRegexes) ||
352        fileSearch(this->PostIncludeFiles)) &&
353      (regexSearch(this->PostExcludeRegexes) ||
354       fileSearch(this->PostExcludeFiles)));
355 }
356 
AddResolvedPath(const std::string & name,const std::string & path,bool & unique,std::vector<std::string> rpaths)357 void cmRuntimeDependencyArchive::AddResolvedPath(
358   const std::string& name, const std::string& path, bool& unique,
359   std::vector<std::string> rpaths)
360 {
361   auto it = this->ResolvedPaths.emplace(name, std::set<std::string>{}).first;
362   unique = true;
363   for (auto const& other : it->second) {
364     if (cmSystemTools::SameFile(path, other)) {
365       unique = false;
366       break;
367     }
368   }
369   it->second.insert(path);
370   this->RPaths[path] = std::move(rpaths);
371 }
372 
AddUnresolvedPath(const std::string & name)373 void cmRuntimeDependencyArchive::AddUnresolvedPath(const std::string& name)
374 {
375   this->UnresolvedPaths.insert(name);
376 }
377 
GetMakefile() const378 cmMakefile* cmRuntimeDependencyArchive::GetMakefile() const
379 {
380   return &this->Status.GetMakefile();
381 }
382 
383 const std::map<std::string, std::set<std::string>>&
GetResolvedPaths() const384 cmRuntimeDependencyArchive::GetResolvedPaths() const
385 {
386   return this->ResolvedPaths;
387 }
388 
GetUnresolvedPaths() const389 const std::set<std::string>& cmRuntimeDependencyArchive::GetUnresolvedPaths()
390   const
391 {
392   return this->UnresolvedPaths;
393 }
394 
395 const std::map<std::string, std::vector<std::string>>&
GetRPaths() const396 cmRuntimeDependencyArchive::GetRPaths() const
397 {
398   return this->RPaths;
399 }
400 
PlatformSupportsRuntimeDependencies(const std::string & platform)401 bool cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
402   const std::string& platform)
403 {
404   static const std::set<std::string> supportedPlatforms = { "Windows", "Linux",
405                                                             "Darwin" };
406   return supportedPlatforms.count(platform);
407 }
408