cmCPackArchiveGenerator.cxx 18 KB

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