cmFileInstaller.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmFileInstaller.h"
  4. #include <map>
  5. #include <sstream>
  6. #include <utility>
  7. #include <vector>
  8. #include <cm/string_view>
  9. #include <cmext/string_view>
  10. #include "cm_sys_stat.h"
  11. #include "cmExecutionStatus.h"
  12. #include "cmFSPermissions.h"
  13. #include "cmMakefile.h"
  14. #include "cmStringAlgorithms.h"
  15. #include "cmSystemTools.h"
  16. #include "cmValue.h"
  17. using namespace cmFSPermissions;
  18. cmFileInstaller::cmFileInstaller(cmExecutionStatus& status)
  19. : cmFileCopier(status, "INSTALL")
  20. {
  21. // Installation does not use source permissions by default.
  22. this->UseSourcePermissions = false;
  23. // Check whether to copy files always or only if they have changed.
  24. std::string install_always;
  25. if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) {
  26. this->Always = cmIsOn(install_always);
  27. }
  28. // Get the current manifest.
  29. this->Manifest =
  30. this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES");
  31. }
  32. cmFileInstaller::~cmFileInstaller()
  33. {
  34. // Save the updated install manifest.
  35. this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES",
  36. this->Manifest);
  37. }
  38. void cmFileInstaller::ManifestAppend(std::string const& file)
  39. {
  40. if (!this->Manifest.empty()) {
  41. this->Manifest += ";";
  42. }
  43. this->Manifest += file.substr(this->DestDirLength);
  44. }
  45. std::string const& cmFileInstaller::ToName(std::string const& fromName)
  46. {
  47. return this->Rename.empty() ? fromName : this->Rename;
  48. }
  49. void cmFileInstaller::ReportCopy(std::string const& toFile, Type type,
  50. bool copy)
  51. {
  52. if (!this->MessageNever && (copy || !this->MessageLazy)) {
  53. std::string message =
  54. cmStrCat((copy ? "Installing: " : "Up-to-date: "), toFile);
  55. this->Makefile->DisplayStatus(message, -1);
  56. }
  57. if (type != TypeDir) {
  58. // Add the file to the manifest.
  59. this->ManifestAppend(toFile);
  60. }
  61. }
  62. bool cmFileInstaller::ReportMissing(std::string const& fromFile)
  63. {
  64. return (this->Optional || this->cmFileCopier::ReportMissing(fromFile));
  65. }
  66. bool cmFileInstaller::Install(std::string const& fromFile,
  67. std::string const& toFile)
  68. {
  69. // Support installing from empty source to make a directory.
  70. if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) {
  71. return this->InstallDirectory(fromFile, toFile, MatchProperties());
  72. }
  73. return this->cmFileCopier::Install(fromFile, toFile);
  74. }
  75. bool cmFileInstaller::InstallFile(std::string const& fromFile,
  76. std::string const& toFile,
  77. MatchProperties match_properties)
  78. {
  79. if (this->InstallMode == cmInstallMode::COPY) {
  80. return this->cmFileCopier::InstallFile(fromFile, toFile, match_properties);
  81. }
  82. std::string newFromFile;
  83. if (this->InstallMode == cmInstallMode::REL_SYMLINK ||
  84. this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
  85. this->InstallMode == cmInstallMode::SYMLINK ||
  86. this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
  87. // Try to get a relative path.
  88. std::string toDir = cmSystemTools::GetParentDirectory(toFile);
  89. newFromFile = cmSystemTools::ForceToRelativePath(toDir, fromFile);
  90. // Double check that we can restore the original path.
  91. std::string reassembled =
  92. cmSystemTools::CollapseFullPath(newFromFile, toDir);
  93. if (!cmSystemTools::ComparePath(reassembled, fromFile)) {
  94. if (this->InstallMode == cmInstallMode::SYMLINK ||
  95. this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
  96. // User does not mind, silently proceed with absolute path.
  97. newFromFile = fromFile;
  98. } else if (this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY) {
  99. // User expects a relative symbolic link or a copy.
  100. // Since an absolute symlink won't do, copy instead.
  101. return this->cmFileCopier::InstallFile(fromFile, toFile,
  102. match_properties);
  103. } else {
  104. // We cannot meet user's expectation (REL_SYMLINK)
  105. auto e = cmStrCat(this->Name,
  106. " cannot determine relative path for symlink to \"",
  107. newFromFile, "\" at \"", toFile, "\".");
  108. this->Status.SetError(e);
  109. return false;
  110. }
  111. }
  112. } else {
  113. newFromFile = fromFile; // stick with absolute path
  114. }
  115. // Compare the symlink value to that at the destination if not
  116. // always installing.
  117. bool copy = true;
  118. if (!this->Always) {
  119. std::string oldSymlinkTarget;
  120. if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
  121. if (newFromFile == oldSymlinkTarget) {
  122. copy = false;
  123. }
  124. }
  125. }
  126. // Inform the user about this file installation.
  127. this->ReportCopy(toFile, TypeLink, copy);
  128. if (copy) {
  129. // Remove the destination file so we can always create the symlink.
  130. cmSystemTools::RemoveFile(toFile);
  131. // Create destination directory if it doesn't exist
  132. cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
  133. // Create the symlink.
  134. if (!cmSystemTools::CreateSymlink(newFromFile, toFile)) {
  135. if (this->InstallMode == cmInstallMode::ABS_SYMLINK_OR_COPY ||
  136. this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
  137. this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
  138. // Failed to create a symbolic link, fall back to copying.
  139. return this->cmFileCopier::InstallFile(newFromFile, toFile,
  140. match_properties);
  141. }
  142. auto e = cmStrCat(this->Name, " cannot create symlink to \"",
  143. newFromFile, "\" at \"", toFile,
  144. "\": ", cmSystemTools::GetLastSystemError(), "\".");
  145. this->Status.SetError(e);
  146. return false;
  147. }
  148. }
  149. return true;
  150. }
  151. void cmFileInstaller::DefaultFilePermissions()
  152. {
  153. this->cmFileCopier::DefaultFilePermissions();
  154. // Add execute permissions based on the target type.
  155. switch (this->InstallType) {
  156. case cmInstallType_SHARED_LIBRARY:
  157. case cmInstallType_MODULE_LIBRARY:
  158. if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) {
  159. break;
  160. }
  161. CM_FALLTHROUGH;
  162. case cmInstallType_EXECUTABLE:
  163. case cmInstallType_PROGRAMS:
  164. this->FilePermissions |= mode_owner_execute;
  165. this->FilePermissions |= mode_group_execute;
  166. this->FilePermissions |= mode_world_execute;
  167. break;
  168. default:
  169. break;
  170. }
  171. }
  172. bool cmFileInstaller::Parse(std::vector<std::string> const& args)
  173. {
  174. if (!this->cmFileCopier::Parse(args)) {
  175. return false;
  176. }
  177. if (!this->Rename.empty()) {
  178. if (!this->FilesFromDir.empty()) {
  179. this->Status.SetError("INSTALL option RENAME may not be "
  180. "combined with FILES_FROM_DIR.");
  181. return false;
  182. }
  183. if (this->InstallType != cmInstallType_FILES &&
  184. this->InstallType != cmInstallType_PROGRAMS) {
  185. this->Status.SetError("INSTALL option RENAME may be used "
  186. "only with FILES or PROGRAMS.");
  187. return false;
  188. }
  189. if (this->Files.size() > 1) {
  190. this->Status.SetError("INSTALL option RENAME may be used "
  191. "only with one file.");
  192. return false;
  193. }
  194. }
  195. if (!this->HandleInstallDestination()) {
  196. return false;
  197. }
  198. if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
  199. (this->MessageNever ? 1 : 0)) > 1) {
  200. this->Status.SetError("INSTALL options MESSAGE_ALWAYS, "
  201. "MESSAGE_LAZY, and MESSAGE_NEVER "
  202. "are mutually exclusive.");
  203. return false;
  204. }
  205. static std::map<cm::string_view, cmInstallMode> const install_mode_dict{
  206. { "ABS_SYMLINK"_s, cmInstallMode::ABS_SYMLINK },
  207. { "ABS_SYMLINK_OR_COPY"_s, cmInstallMode::ABS_SYMLINK_OR_COPY },
  208. { "REL_SYMLINK"_s, cmInstallMode::REL_SYMLINK },
  209. { "REL_SYMLINK_OR_COPY"_s, cmInstallMode::REL_SYMLINK_OR_COPY },
  210. { "SYMLINK"_s, cmInstallMode::SYMLINK },
  211. { "SYMLINK_OR_COPY"_s, cmInstallMode::SYMLINK_OR_COPY }
  212. };
  213. std::string install_mode;
  214. cmSystemTools::GetEnv("CMAKE_INSTALL_MODE", install_mode);
  215. if (install_mode.empty() || install_mode == "COPY"_s) {
  216. this->InstallMode = cmInstallMode::COPY;
  217. } else {
  218. auto it = install_mode_dict.find(install_mode);
  219. if (it != install_mode_dict.end()) {
  220. this->InstallMode = it->second;
  221. } else {
  222. auto e = cmStrCat("Unrecognized value '", install_mode,
  223. "' for environment variable CMAKE_INSTALL_MODE");
  224. this->Status.SetError(e);
  225. return false;
  226. }
  227. }
  228. return true;
  229. }
  230. bool cmFileInstaller::CheckKeyword(std::string const& arg)
  231. {
  232. if (arg == "TYPE") {
  233. if (this->CurrentMatchRule) {
  234. this->NotAfterMatch(arg);
  235. } else {
  236. this->Doing = DoingType;
  237. }
  238. } else if (arg == "FILES") {
  239. if (this->CurrentMatchRule) {
  240. this->NotAfterMatch(arg);
  241. } else {
  242. this->Doing = DoingFiles;
  243. }
  244. } else if (arg == "RENAME") {
  245. if (this->CurrentMatchRule) {
  246. this->NotAfterMatch(arg);
  247. } else {
  248. this->Doing = DoingRename;
  249. }
  250. } else if (arg == "OPTIONAL") {
  251. if (this->CurrentMatchRule) {
  252. this->NotAfterMatch(arg);
  253. } else {
  254. this->Doing = DoingNone;
  255. this->Optional = true;
  256. }
  257. } else if (arg == "MESSAGE_ALWAYS") {
  258. if (this->CurrentMatchRule) {
  259. this->NotAfterMatch(arg);
  260. } else {
  261. this->Doing = DoingNone;
  262. this->MessageAlways = true;
  263. }
  264. } else if (arg == "MESSAGE_LAZY") {
  265. if (this->CurrentMatchRule) {
  266. this->NotAfterMatch(arg);
  267. } else {
  268. this->Doing = DoingNone;
  269. this->MessageLazy = true;
  270. }
  271. } else if (arg == "MESSAGE_NEVER") {
  272. if (this->CurrentMatchRule) {
  273. this->NotAfterMatch(arg);
  274. } else {
  275. this->Doing = DoingNone;
  276. this->MessageNever = true;
  277. }
  278. } else if (arg == "PERMISSIONS") {
  279. if (this->CurrentMatchRule) {
  280. this->Doing = DoingPermissionsMatch;
  281. } else {
  282. // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS
  283. this->Doing = DoingPermissionsFile;
  284. this->UseGivenPermissionsFile = true;
  285. }
  286. } else if (arg == "DIR_PERMISSIONS") {
  287. if (this->CurrentMatchRule) {
  288. this->NotAfterMatch(arg);
  289. } else {
  290. // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS
  291. this->Doing = DoingPermissionsDir;
  292. this->UseGivenPermissionsDir = true;
  293. }
  294. } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" ||
  295. arg == "PROPERTIES") {
  296. std::ostringstream e;
  297. e << "INSTALL called with old-style " << arg << " argument. "
  298. << "This script was generated with an older version of CMake. "
  299. << "Re-run this cmake version on your build tree.";
  300. this->Status.SetError(e.str());
  301. this->Doing = DoingError;
  302. } else {
  303. return this->cmFileCopier::CheckKeyword(arg);
  304. }
  305. return true;
  306. }
  307. bool cmFileInstaller::CheckValue(std::string const& arg)
  308. {
  309. switch (this->Doing) {
  310. case DoingType:
  311. if (!this->GetTargetTypeFromString(arg)) {
  312. this->Doing = DoingError;
  313. }
  314. break;
  315. case DoingRename:
  316. this->Rename = arg;
  317. break;
  318. default:
  319. return this->cmFileCopier::CheckValue(arg);
  320. }
  321. return true;
  322. }
  323. bool cmFileInstaller::GetTargetTypeFromString(std::string const& stype)
  324. {
  325. if (stype == "EXECUTABLE") {
  326. this->InstallType = cmInstallType_EXECUTABLE;
  327. } else if (stype == "FILE") {
  328. this->InstallType = cmInstallType_FILES;
  329. } else if (stype == "PROGRAM") {
  330. this->InstallType = cmInstallType_PROGRAMS;
  331. } else if (stype == "STATIC_LIBRARY") {
  332. this->InstallType = cmInstallType_STATIC_LIBRARY;
  333. } else if (stype == "SHARED_LIBRARY") {
  334. this->InstallType = cmInstallType_SHARED_LIBRARY;
  335. } else if (stype == "MODULE") {
  336. this->InstallType = cmInstallType_MODULE_LIBRARY;
  337. } else if (stype == "DIRECTORY") {
  338. this->InstallType = cmInstallType_DIRECTORY;
  339. } else {
  340. std::ostringstream e;
  341. e << "Option TYPE given unknown value \"" << stype << "\".";
  342. this->Status.SetError(e.str());
  343. return false;
  344. }
  345. return true;
  346. }
  347. bool cmFileInstaller::HandleInstallDestination()
  348. {
  349. std::string& destination = this->Destination;
  350. // allow for / to be a valid destination
  351. if (destination.size() < 2 && destination != "/") {
  352. this->Status.SetError("called with inappropriate arguments. "
  353. "No DESTINATION provided or .");
  354. return false;
  355. }
  356. std::string sdestdir;
  357. if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) {
  358. cmSystemTools::ConvertToUnixSlashes(sdestdir);
  359. char ch1 = destination[0];
  360. char ch2 = destination[1];
  361. char ch3 = 0;
  362. if (destination.size() > 2) {
  363. ch3 = destination[2];
  364. }
  365. int skip = 0;
  366. if (ch1 != '/') {
  367. int relative = 0;
  368. if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) &&
  369. ch2 == ':') {
  370. // Assume windows
  371. // let's do some destdir magic:
  372. skip = 2;
  373. if (ch3 != '/') {
  374. relative = 1;
  375. }
  376. } else {
  377. relative = 1;
  378. }
  379. if (relative) {
  380. // This is relative path on unix or windows. Since we are doing
  381. // destdir, this case does not make sense.
  382. this->Status.SetError(
  383. "called with relative DESTINATION. This "
  384. "does not make sense when using DESTDIR. Specify "
  385. "absolute path or remove DESTDIR environment variable.");
  386. return false;
  387. }
  388. } else {
  389. if (ch2 == '/') {
  390. // looks like a network path.
  391. std::string message =
  392. cmStrCat("called with network path DESTINATION. This "
  393. "does not make sense when using DESTDIR. Specify local "
  394. "absolute path or remove DESTDIR environment variable."
  395. "\nDESTINATION=\n",
  396. destination);
  397. this->Status.SetError(message);
  398. return false;
  399. }
  400. }
  401. destination = sdestdir + destination.substr(skip);
  402. this->DestDirLength = static_cast<int>(sdestdir.size());
  403. }
  404. // check if default dir creation permissions were set
  405. mode_t default_dir_mode_v = 0;
  406. mode_t* default_dir_mode = &default_dir_mode_v;
  407. if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
  408. return false;
  409. }
  410. if (this->InstallType != cmInstallType_DIRECTORY) {
  411. if (!cmSystemTools::FileExists(destination)) {
  412. if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
  413. std::string errstring = "cannot create directory: " + destination +
  414. ". Maybe need administrative privileges.";
  415. this->Status.SetError(errstring);
  416. return false;
  417. }
  418. }
  419. if (!cmSystemTools::FileIsDirectory(destination)) {
  420. std::string errstring =
  421. "INSTALL destination: " + destination + " is not a directory.";
  422. this->Status.SetError(errstring);
  423. return false;
  424. }
  425. }
  426. return true;
  427. }