cmFileInstaller.cxx 15 KB

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