cmCPackArchiveGenerator.cxx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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 "cmCPackArchiveGenerator.h"
  4. #include <cstring>
  5. #include <map>
  6. #include <ostream>
  7. #include <unordered_map>
  8. #include <unordered_set>
  9. #include <utility>
  10. #include <vector>
  11. #include "cmCPackComponentGroup.h"
  12. #include "cmCPackGenerator.h"
  13. #include "cmCPackLog.h"
  14. #include "cmGeneratedFileStream.h"
  15. #include "cmStringAlgorithms.h"
  16. #include "cmSystemTools.h"
  17. #include "cmValue.h"
  18. #include "cmWorkingDirectory.h"
  19. enum class DeduplicateStatus
  20. {
  21. Skip,
  22. Add,
  23. Error
  24. };
  25. /**
  26. * @class cmCPackArchiveGenerator::Deduplicator
  27. * @brief A utility class for deduplicating files, folders, and symlinks.
  28. *
  29. * This class is responsible for identifying duplicate files, folders, and
  30. * symlinks when generating an archive. It keeps track of the paths that have
  31. * been processed and helps in deciding whether a new path should be added,
  32. * skipped, or flagged as an error.
  33. */
  34. class cmCPackArchiveGenerator::Deduplicator
  35. {
  36. private:
  37. /**
  38. * @brief Compares a file with already processed files.
  39. *
  40. * @param path The path of the file to compare.
  41. * @param localTopLevel The top-level directory for the file.
  42. * @return DeduplicateStatus indicating whether to add, skip, or flag an
  43. * error for the file.
  44. */
  45. DeduplicateStatus CompareFile(const std::string& path,
  46. const std::string& localTopLevel)
  47. {
  48. auto fileItr = this->Files.find(path);
  49. if (fileItr != this->Files.end()) {
  50. return cmSystemTools::FilesDiffer(path, fileItr->second)
  51. ? DeduplicateStatus::Error
  52. : DeduplicateStatus::Skip;
  53. }
  54. this->Files[path] = cmStrCat(localTopLevel, "/", path);
  55. return DeduplicateStatus::Add;
  56. }
  57. /**
  58. * @brief Compares a folder with already processed folders.
  59. *
  60. * @param path The path of the folder to compare.
  61. * @return DeduplicateStatus indicating whether to add or skip the folder.
  62. */
  63. DeduplicateStatus CompareFolder(const std::string& path)
  64. {
  65. if (this->Folders.find(path) != this->Folders.end()) {
  66. return DeduplicateStatus::Skip;
  67. }
  68. this->Folders.emplace(path);
  69. return DeduplicateStatus::Add;
  70. }
  71. /**
  72. * @brief Compares a symlink with already processed symlinks.
  73. *
  74. * @param path The path of the symlink to compare.
  75. * @return DeduplicateStatus indicating whether to add, skip, or flag an
  76. * error for the symlink.
  77. */
  78. DeduplicateStatus CompareSymlink(const std::string& path)
  79. {
  80. auto symlinkItr = this->Symlink.find(path);
  81. std::string symlinkValue;
  82. auto status = cmSystemTools::ReadSymlink(path, symlinkValue);
  83. if (!status.IsSuccess()) {
  84. return DeduplicateStatus::Error;
  85. }
  86. if (symlinkItr != this->Symlink.end()) {
  87. return symlinkValue == symlinkItr->second ? DeduplicateStatus::Skip
  88. : DeduplicateStatus::Error;
  89. }
  90. this->Symlink[path] = symlinkValue;
  91. return DeduplicateStatus::Add;
  92. }
  93. public:
  94. /**
  95. * @brief Determines the deduplication status of a given path.
  96. *
  97. * This method identifies whether the given path is a file, folder, or
  98. * symlink and then delegates to the appropriate comparison method.
  99. *
  100. * @param path The path to check for deduplication.
  101. * @param localTopLevel The top-level directory for the path.
  102. * @return DeduplicateStatus indicating the action to take for the given
  103. * path.
  104. */
  105. DeduplicateStatus IsDeduplicate(const std::string& path,
  106. const std::string& localTopLevel)
  107. {
  108. DeduplicateStatus status;
  109. if (cmSystemTools::FileIsDirectory(path)) {
  110. status = this->CompareFolder(path);
  111. } else if (cmSystemTools::FileIsSymlink(path)) {
  112. status = this->CompareSymlink(path);
  113. } else {
  114. status = this->CompareFile(path, localTopLevel);
  115. }
  116. return status;
  117. }
  118. private:
  119. std::unordered_map<std::string, std::string> Symlink;
  120. std::unordered_set<std::string> Folders;
  121. std::unordered_map<std::string, std::string> Files;
  122. };
  123. cmCPackGenerator* cmCPackArchiveGenerator::Create7ZGenerator()
  124. {
  125. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "7zip",
  126. ".7z");
  127. }
  128. cmCPackGenerator* cmCPackArchiveGenerator::CreateTBZ2Generator()
  129. {
  130. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressBZip2, "paxr",
  131. ".tar.bz2");
  132. }
  133. cmCPackGenerator* cmCPackArchiveGenerator::CreateTGZGenerator()
  134. {
  135. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressGZip, "paxr",
  136. ".tar.gz");
  137. }
  138. cmCPackGenerator* cmCPackArchiveGenerator::CreateTXZGenerator()
  139. {
  140. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr",
  141. ".tar.xz");
  142. }
  143. cmCPackGenerator* cmCPackArchiveGenerator::CreateTZGenerator()
  144. {
  145. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressCompress, "paxr",
  146. ".tar.Z");
  147. }
  148. cmCPackGenerator* cmCPackArchiveGenerator::CreateTZSTGenerator()
  149. {
  150. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressZstd, "paxr",
  151. ".tar.zst");
  152. }
  153. cmCPackGenerator* cmCPackArchiveGenerator::CreateZIPGenerator()
  154. {
  155. return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "zip",
  156. ".zip");
  157. }
  158. cmCPackArchiveGenerator::cmCPackArchiveGenerator(
  159. cmArchiveWrite::Compress compress, std::string format, std::string extension)
  160. : Compress(compress)
  161. , ArchiveFormat(std::move(format))
  162. , OutputExtension(std::move(extension))
  163. {
  164. }
  165. cmCPackArchiveGenerator::~cmCPackArchiveGenerator() = default;
  166. std::string cmCPackArchiveGenerator::GetArchiveComponentFileName(
  167. const std::string& component, bool isGroupName)
  168. {
  169. std::string componentUpper(cmSystemTools::UpperCase(component));
  170. std::string packageFileName;
  171. if (this->IsSet("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME")) {
  172. packageFileName +=
  173. *this->GetOption("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME");
  174. } else if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) {
  175. packageFileName += this->GetComponentPackageFileName(
  176. *this->GetOption("CPACK_ARCHIVE_FILE_NAME"), component, isGroupName);
  177. } else {
  178. packageFileName += this->GetComponentPackageFileName(
  179. *this->GetOption("CPACK_PACKAGE_FILE_NAME"), component, isGroupName);
  180. }
  181. packageFileName += this->GetOutputExtension();
  182. return packageFileName;
  183. }
  184. int cmCPackArchiveGenerator::InitializeInternal()
  185. {
  186. this->SetOptionIfNotSet("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "1");
  187. cmValue newExtensionValue = this->GetOption("CPACK_ARCHIVE_FILE_EXTENSION");
  188. if (!newExtensionValue.IsEmpty()) {
  189. std::string newExtension = *newExtensionValue;
  190. if (!cmHasLiteralPrefix(newExtension, ".")) {
  191. newExtension = cmStrCat('.', newExtension);
  192. }
  193. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  194. "Using user-provided file extension "
  195. << newExtension << " instead of the default "
  196. << this->OutputExtension << std::endl);
  197. this->OutputExtension = std::move(newExtension);
  198. }
  199. return this->Superclass::InitializeInternal();
  200. }
  201. int cmCPackArchiveGenerator::addOneComponentToArchive(
  202. cmArchiveWrite& archive, cmCPackComponent* component,
  203. Deduplicator* deduplicator)
  204. {
  205. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  206. " - packaging component: " << component->Name << std::endl);
  207. // Add the files of this component to the archive
  208. std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
  209. localToplevel += "/" + this->GetSanitizedDirOrFileName(component->Name);
  210. // Change to local toplevel
  211. cmWorkingDirectory workdir(localToplevel);
  212. if (workdir.Failed()) {
  213. cmCPackLogger(cmCPackLog::LOG_ERROR,
  214. "Failed to change working directory to "
  215. << localToplevel << " : "
  216. << std::strerror(workdir.GetLastResult()) << std::endl);
  217. return 0;
  218. }
  219. std::string filePrefix;
  220. if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY")) {
  221. filePrefix = cmStrCat(this->GetOption("CPACK_PACKAGE_FILE_NAME"), '/');
  222. }
  223. cmValue installPrefix = this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX");
  224. if (installPrefix && installPrefix->size() > 1 &&
  225. (*installPrefix)[0] == '/') {
  226. // add to file prefix and remove the leading '/'
  227. filePrefix += installPrefix->substr(1);
  228. filePrefix += "/";
  229. }
  230. for (std::string const& file : component->Files) {
  231. std::string rp = filePrefix + file;
  232. DeduplicateStatus status = DeduplicateStatus::Add;
  233. if (deduplicator != nullptr) {
  234. status = deduplicator->IsDeduplicate(rp, localToplevel);
  235. }
  236. if (deduplicator == nullptr || status == DeduplicateStatus::Add) {
  237. cmCPackLogger(cmCPackLog::LOG_DEBUG, "Adding file: " << rp << std::endl);
  238. archive.Add(rp, 0, nullptr, false);
  239. } else if (status == DeduplicateStatus::Error) {
  240. cmCPackLogger(cmCPackLog::LOG_ERROR,
  241. "ERROR The data in files with the "
  242. "same filename is different.");
  243. return 0;
  244. } else {
  245. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  246. "Passing file: " << rp << std::endl);
  247. }
  248. if (!archive) {
  249. cmCPackLogger(cmCPackLog::LOG_ERROR,
  250. "ERROR while packaging files: " << archive.GetError()
  251. << std::endl);
  252. return 0;
  253. }
  254. }
  255. return 1;
  256. }
  257. /*
  258. * The macro will open/create a file 'filename'
  259. * an declare and open the associated
  260. * cmArchiveWrite 'archive' object.
  261. */
  262. #define DECLARE_AND_OPEN_ARCHIVE(filename, archive) \
  263. cmGeneratedFileStream gf; \
  264. gf.Open((filename), false, true); \
  265. if (!GenerateHeader(&gf)) { \
  266. cmCPackLogger(cmCPackLog::LOG_ERROR, \
  267. "Problem to generate Header for archive <" \
  268. << (filename) << ">." << std::endl); \
  269. return 0; \
  270. } \
  271. cmArchiveWrite archive(gf, this->Compress, this->ArchiveFormat, 0, \
  272. this->GetThreadCount()); \
  273. do { \
  274. if (!archive.Open()) { \
  275. cmCPackLogger(cmCPackLog::LOG_ERROR, \
  276. "Problem to open archive <" \
  277. << (filename) << ">, ERROR = " << (archive).GetError() \
  278. << std::endl); \
  279. return 0; \
  280. } \
  281. if (!(archive)) { \
  282. cmCPackLogger(cmCPackLog::LOG_ERROR, \
  283. "Problem to create archive <" \
  284. << (filename) << ">, ERROR = " << (archive).GetError() \
  285. << std::endl); \
  286. return 0; \
  287. } \
  288. } while (false)
  289. int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup)
  290. {
  291. this->packageFileNames.clear();
  292. // The default behavior is to have one package by component group
  293. // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
  294. if (!ignoreGroup) {
  295. for (auto const& compG : this->ComponentGroups) {
  296. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  297. "Packaging component group: " << compG.first << std::endl);
  298. // Begin the archive for this group
  299. std::string packageFileName = std::string(this->toplevel) + "/" +
  300. this->GetArchiveComponentFileName(compG.first, true);
  301. Deduplicator deduplicator;
  302. // open a block in order to automatically close archive
  303. // at the end of the block
  304. {
  305. DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
  306. // now iterate over the component of this group
  307. for (cmCPackComponent* comp : (compG.second).Components) {
  308. // Add the files of this component to the archive
  309. this->addOneComponentToArchive(archive, comp, &deduplicator);
  310. }
  311. }
  312. // add the generated package to package file names list
  313. this->packageFileNames.push_back(std::move(packageFileName));
  314. }
  315. // Handle Orphan components (components not belonging to any groups)
  316. for (auto& comp : this->Components) {
  317. // Does the component belong to a group?
  318. if (comp.second.Group == nullptr) {
  319. cmCPackLogger(
  320. cmCPackLog::LOG_VERBOSE,
  321. "Component <"
  322. << comp.second.Name
  323. << "> does not belong to any group, package it separately."
  324. << std::endl);
  325. std::string packageFileName = std::string(this->toplevel);
  326. packageFileName +=
  327. "/" + this->GetArchiveComponentFileName(comp.first, false);
  328. {
  329. DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
  330. // Add the files of this component to the archive
  331. this->addOneComponentToArchive(archive, &(comp.second), nullptr);
  332. }
  333. // add the generated package to package file names list
  334. this->packageFileNames.push_back(std::move(packageFileName));
  335. }
  336. }
  337. }
  338. // CPACK_COMPONENTS_IGNORE_GROUPS is set
  339. // We build 1 package per component
  340. else {
  341. for (auto& comp : this->Components) {
  342. std::string packageFileName = std::string(this->toplevel);
  343. packageFileName +=
  344. "/" + this->GetArchiveComponentFileName(comp.first, false);
  345. {
  346. DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
  347. // Add the files of this component to the archive
  348. this->addOneComponentToArchive(archive, &(comp.second), nullptr);
  349. }
  350. // add the generated package to package file names list
  351. this->packageFileNames.push_back(std::move(packageFileName));
  352. }
  353. }
  354. return 1;
  355. }
  356. int cmCPackArchiveGenerator::PackageComponentsAllInOne()
  357. {
  358. // reset the package file names
  359. this->packageFileNames.clear();
  360. this->packageFileNames.emplace_back(this->toplevel);
  361. this->packageFileNames[0] += "/";
  362. if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) {
  363. this->packageFileNames[0] += *this->GetOption("CPACK_ARCHIVE_FILE_NAME");
  364. } else {
  365. this->packageFileNames[0] += *this->GetOption("CPACK_PACKAGE_FILE_NAME");
  366. }
  367. this->packageFileNames[0] += this->GetOutputExtension();
  368. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  369. "Packaging all groups in one package..."
  370. "(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE is set)"
  371. << std::endl);
  372. DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
  373. Deduplicator deduplicator;
  374. // The ALL COMPONENTS in ONE package case
  375. for (auto& comp : this->Components) {
  376. // Add the files of this component to the archive
  377. this->addOneComponentToArchive(archive, &(comp.second), &deduplicator);
  378. }
  379. // archive goes out of scope so it will finalized and closed.
  380. return 1;
  381. }
  382. int cmCPackArchiveGenerator::PackageFiles()
  383. {
  384. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  385. "Toplevel: " << this->toplevel << std::endl);
  386. if (this->WantsComponentInstallation()) {
  387. // CASE 1 : COMPONENT ALL-IN-ONE package
  388. // If ALL COMPONENTS in ONE package has been requested
  389. // then the package file is unique and should be open here.
  390. if (this->componentPackageMethod == ONE_PACKAGE) {
  391. return this->PackageComponentsAllInOne();
  392. }
  393. // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
  394. // There will be 1 package for each component group
  395. // however one may require to ignore component group and
  396. // in this case you'll get 1 package for each component.
  397. return this->PackageComponents(this->componentPackageMethod ==
  398. ONE_PACKAGE_PER_COMPONENT);
  399. }
  400. // CASE 3 : NON COMPONENT package.
  401. DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
  402. cmWorkingDirectory workdir(this->toplevel);
  403. if (workdir.Failed()) {
  404. cmCPackLogger(cmCPackLog::LOG_ERROR,
  405. "Failed to change working directory to "
  406. << this->toplevel << " : "
  407. << std::strerror(workdir.GetLastResult()) << std::endl);
  408. return 0;
  409. }
  410. for (std::string const& file : this->files) {
  411. // Get the relative path to the file
  412. std::string rp = cmSystemTools::RelativePath(this->toplevel, file);
  413. archive.Add(rp, 0, nullptr, false);
  414. if (!archive) {
  415. cmCPackLogger(cmCPackLog::LOG_ERROR,
  416. "Problem while adding file <"
  417. << file << "> to archive <" << this->packageFileNames[0]
  418. << ">, ERROR = " << archive.GetError() << std::endl);
  419. return 0;
  420. }
  421. }
  422. // The destructor of cmArchiveWrite will close and finish the write
  423. return 1;
  424. }
  425. int cmCPackArchiveGenerator::GenerateHeader(std::ostream* /*unused*/)
  426. {
  427. return 1;
  428. }
  429. bool cmCPackArchiveGenerator::SupportsComponentInstallation() const
  430. {
  431. // The Component installation support should only
  432. // be activated if explicitly requested by the user
  433. // (for backward compatibility reason)
  434. return this->IsOn("CPACK_ARCHIVE_COMPONENT_INSTALL");
  435. }
  436. int cmCPackArchiveGenerator::GetThreadCount() const
  437. {
  438. int threads = 1;
  439. // CPACK_ARCHIVE_THREADS overrides CPACK_THREADS
  440. if (this->IsSet("CPACK_ARCHIVE_THREADS")) {
  441. threads = std::stoi(*this->GetOption("CPACK_ARCHIVE_THREADS"));
  442. } else if (this->IsSet("CPACK_THREADS")) {
  443. threads = std::stoi(*this->GetOption("CPACK_THREADS"));
  444. }
  445. return threads;
  446. }