| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file LICENSE.rst or https://cmake.org/licensing for details. */
- #include "cmCPackArchiveGenerator.h"
- #include <map>
- #include <ostream>
- #include <unordered_map>
- #include <unordered_set>
- #include <utility>
- #include <vector>
- #include "cmCPackComponentGroup.h"
- #include "cmCPackGenerator.h"
- #include "cmCPackLog.h"
- #include "cmGeneratedFileStream.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- #include "cmValue.h"
- #include "cmWorkingDirectory.h"
- enum class DeduplicateStatus
- {
- Skip,
- Add,
- Error
- };
- /**
- * @class cmCPackArchiveGenerator::Deduplicator
- * @brief A utility class for deduplicating files, folders, and symlinks.
- *
- * This class is responsible for identifying duplicate files, folders, and
- * symlinks when generating an archive. It keeps track of the paths that have
- * been processed and helps in deciding whether a new path should be added,
- * skipped, or flagged as an error.
- */
- class cmCPackArchiveGenerator::Deduplicator
- {
- private:
- /**
- * @brief Compares a file with already processed files.
- *
- * @param path The path of the file to compare.
- * @param localTopLevel The top-level directory for the file.
- * @return DeduplicateStatus indicating whether to add, skip, or flag an
- * error for the file.
- */
- DeduplicateStatus CompareFile(std::string const& path,
- std::string const& localTopLevel)
- {
- auto fileItr = this->Files.find(path);
- if (fileItr != this->Files.end()) {
- return cmSystemTools::FilesDiffer(path, fileItr->second)
- ? DeduplicateStatus::Error
- : DeduplicateStatus::Skip;
- }
- this->Files[path] = cmStrCat(localTopLevel, '/', path);
- return DeduplicateStatus::Add;
- }
- /**
- * @brief Compares a folder with already processed folders.
- *
- * @param path The path of the folder to compare.
- * @return DeduplicateStatus indicating whether to add or skip the folder.
- */
- DeduplicateStatus CompareFolder(std::string const& path)
- {
- if (this->Folders.find(path) != this->Folders.end()) {
- return DeduplicateStatus::Skip;
- }
- this->Folders.emplace(path);
- return DeduplicateStatus::Add;
- }
- /**
- * @brief Compares a symlink with already processed symlinks.
- *
- * @param path The path of the symlink to compare.
- * @return DeduplicateStatus indicating whether to add, skip, or flag an
- * error for the symlink.
- */
- DeduplicateStatus CompareSymlink(std::string const& path)
- {
- auto symlinkItr = this->Symlink.find(path);
- std::string symlinkValue;
- auto status = cmSystemTools::ReadSymlink(path, symlinkValue);
- if (!status.IsSuccess()) {
- return DeduplicateStatus::Error;
- }
- if (symlinkItr != this->Symlink.end()) {
- return symlinkValue == symlinkItr->second ? DeduplicateStatus::Skip
- : DeduplicateStatus::Error;
- }
- this->Symlink[path] = symlinkValue;
- return DeduplicateStatus::Add;
- }
- public:
- /**
- * @brief Determines the deduplication status of a given path.
- *
- * This method identifies whether the given path is a file, folder, or
- * symlink and then delegates to the appropriate comparison method.
- *
- * @param path The path to check for deduplication.
- * @param localTopLevel The top-level directory for the path.
- * @return DeduplicateStatus indicating the action to take for the given
- * path.
- */
- DeduplicateStatus IsDeduplicate(std::string const& path,
- std::string const& localTopLevel)
- {
- DeduplicateStatus status;
- if (cmSystemTools::FileIsDirectory(path)) {
- status = this->CompareFolder(path);
- } else if (cmSystemTools::FileIsSymlink(path)) {
- status = this->CompareSymlink(path);
- } else {
- status = this->CompareFile(path, localTopLevel);
- }
- return status;
- }
- private:
- std::unordered_map<std::string, std::string> Symlink;
- std::unordered_set<std::string> Folders;
- std::unordered_map<std::string, std::string> Files;
- };
- cmCPackGenerator* cmCPackArchiveGenerator::Create7ZGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "7zip",
- ".7z");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateTBZ2Generator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressBZip2, "paxr",
- ".tar.bz2");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateTGZGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressGZip, "paxr",
- ".tar.gz");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateTXZGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr",
- ".tar.xz");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateTZGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressCompress, "paxr",
- ".tar.Z");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateTZSTGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressZstd, "paxr",
- ".tar.zst");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateTarGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "gnutar",
- ".tar");
- }
- cmCPackGenerator* cmCPackArchiveGenerator::CreateZIPGenerator()
- {
- return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "zip",
- ".zip");
- }
- cmCPackArchiveGenerator::cmCPackArchiveGenerator(
- cmArchiveWrite::Compress compress, std::string format, std::string extension)
- : Compress(compress)
- , ArchiveFormat(std::move(format))
- , OutputExtension(std::move(extension))
- {
- }
- cmCPackArchiveGenerator::~cmCPackArchiveGenerator() = default;
- std::string cmCPackArchiveGenerator::GetArchiveFileName()
- {
- std::string packageFileName = this->toplevel + "/";
- if (cmValue v = this->GetOptionIfSet("CPACK_ARCHIVE_FILE_NAME")) {
- packageFileName += *v;
- } else {
- v = this->GetOption("CPACK_PACKAGE_FILE_NAME");
- packageFileName += *v;
- }
- packageFileName += this->GetOutputExtension();
- return packageFileName;
- }
- std::string cmCPackArchiveGenerator::GetArchiveComponentFileName(
- std::string const& component, bool isGroupName)
- {
- std::string componentUpper(cmSystemTools::UpperCase(component));
- std::string packageFileName;
- if (cmValue v = this->GetOptionIfSet("CPACK_ARCHIVE_" + componentUpper +
- "_FILE_NAME")) {
- packageFileName += *v;
- } else if ((v = this->GetOptionIfSet("CPACK_ARCHIVE_FILE_NAME"))) {
- packageFileName +=
- this->GetComponentPackageFileName(*v, component, isGroupName);
- } else {
- v = this->GetOption("CPACK_PACKAGE_FILE_NAME");
- packageFileName +=
- this->GetComponentPackageFileName(*v, component, isGroupName);
- }
- packageFileName += this->GetOutputExtension();
- return packageFileName;
- }
- int cmCPackArchiveGenerator::InitializeInternal()
- {
- this->SetOptionIfNotSet("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "1");
- cmValue newExtensionValue = this->GetOption("CPACK_ARCHIVE_FILE_EXTENSION");
- if (!newExtensionValue.IsEmpty()) {
- std::string newExtension = *newExtensionValue;
- if (!cmHasLiteralPrefix(newExtension, ".")) {
- newExtension = cmStrCat('.', newExtension);
- }
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Using user-provided file extension "
- << newExtension << " instead of the default "
- << this->OutputExtension << std::endl);
- this->OutputExtension = std::move(newExtension);
- }
- return this->Superclass::InitializeInternal();
- }
- int cmCPackArchiveGenerator::addOneComponentToArchive(
- cmArchiveWrite& archive, cmCPackComponent* component,
- Deduplicator* deduplicator)
- {
- cmCPackLogger(cmCPackLog::LOG_VERBOSE,
- " - packaging component: " << component->Name << std::endl);
- // Add the files of this component to the archive
- std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
- localToplevel += "/" + this->GetSanitizedDirOrFileName(component->Name);
- // Change to local toplevel
- cmWorkingDirectory workdir(localToplevel);
- if (workdir.Failed()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR, workdir.GetError() << std::endl);
- return 0;
- }
- std::string filePrefix;
- if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY")) {
- filePrefix = cmStrCat(this->GetOption("CPACK_PACKAGE_FILE_NAME"), '/');
- }
- cmValue installPrefix = this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX");
- if (installPrefix && installPrefix->size() > 1 &&
- (*installPrefix)[0] == '/') {
- // add to file prefix and remove the leading '/'
- filePrefix += installPrefix->substr(1);
- filePrefix += "/";
- }
- for (std::string const& file : component->Files) {
- std::string rp = filePrefix + file;
- DeduplicateStatus status = DeduplicateStatus::Add;
- if (deduplicator) {
- status = deduplicator->IsDeduplicate(rp, localToplevel);
- }
- if (!deduplicator || status == DeduplicateStatus::Add) {
- cmCPackLogger(cmCPackLog::LOG_DEBUG, "Adding file: " << rp << std::endl);
- archive.Add(rp, 0, nullptr, false);
- } else if (status == DeduplicateStatus::Error) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "ERROR The data in files with the "
- "same filename is different.");
- return 0;
- } else {
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Passing file: " << rp << std::endl);
- }
- if (!archive) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "ERROR while packaging files: " << archive.GetError()
- << std::endl);
- return 0;
- }
- }
- return 1;
- }
- /*
- * The macro will open/create a file 'filename'
- * an declare and open the associated
- * cmArchiveWrite 'archive' object.
- */
- #define DECLARE_AND_OPEN_ARCHIVE(filename, archive) \
- cmGeneratedFileStream gf; \
- gf.Open((filename), false, true); \
- if (!GenerateHeader(&gf)) { \
- cmCPackLogger(cmCPackLog::LOG_ERROR, \
- "Problem to generate Header for archive <" \
- << (filename) << ">." << std::endl); \
- return 0; \
- } \
- cmArchiveWrite archive(gf, this->Compress, this->ArchiveFormat, 0, \
- this->GetThreadCount()); \
- do { \
- if (!archive.Open()) { \
- cmCPackLogger(cmCPackLog::LOG_ERROR, \
- "Problem to open archive <" \
- << (filename) << ">, ERROR = " << (archive).GetError() \
- << std::endl); \
- return 0; \
- } \
- if (!(archive)) { \
- cmCPackLogger(cmCPackLog::LOG_ERROR, \
- "Problem to create archive <" \
- << (filename) << ">, ERROR = " << (archive).GetError() \
- << std::endl); \
- return 0; \
- } \
- } while (false)
- int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup)
- {
- this->packageFileNames.clear();
- // The default behavior is to have one package by component group
- // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
- if (!ignoreGroup) {
- for (auto const& compG : this->ComponentGroups) {
- cmCPackLogger(cmCPackLog::LOG_VERBOSE,
- "Packaging component group: " << compG.first << std::endl);
- // Begin the archive for this group
- std::string packageFileName = std::string(this->toplevel) + "/" +
- this->GetArchiveComponentFileName(compG.first, true);
- Deduplicator deduplicator;
- // open a block in order to automatically close archive
- // at the end of the block
- {
- DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
- // now iterate over the component of this group
- for (cmCPackComponent* comp : (compG.second).Components) {
- // Add the files of this component to the archive
- this->addOneComponentToArchive(archive, comp, &deduplicator);
- }
- }
- // add the generated package to package file names list
- this->packageFileNames.push_back(std::move(packageFileName));
- }
- // Handle Orphan components (components not belonging to any groups)
- for (auto& comp : this->Components) {
- // Does the component belong to a group?
- if (!comp.second.Group) {
- cmCPackLogger(
- cmCPackLog::LOG_VERBOSE,
- "Component <"
- << comp.second.Name
- << "> does not belong to any group, package it separately."
- << std::endl);
- std::string packageFileName = std::string(this->toplevel);
- packageFileName +=
- "/" + this->GetArchiveComponentFileName(comp.first, false);
- {
- DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
- // Add the files of this component to the archive
- this->addOneComponentToArchive(archive, &(comp.second), nullptr);
- }
- // add the generated package to package file names list
- this->packageFileNames.push_back(std::move(packageFileName));
- }
- }
- }
- // CPACK_COMPONENTS_IGNORE_GROUPS is set
- // We build 1 package per component
- else {
- for (auto& comp : this->Components) {
- std::string packageFileName = std::string(this->toplevel);
- packageFileName +=
- "/" + this->GetArchiveComponentFileName(comp.first, false);
- {
- DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
- // Add the files of this component to the archive
- this->addOneComponentToArchive(archive, &(comp.second), nullptr);
- }
- // add the generated package to package file names list
- this->packageFileNames.push_back(std::move(packageFileName));
- }
- }
- return 1;
- }
- int cmCPackArchiveGenerator::PackageComponentsAllInOne()
- {
- // reset the package file names
- this->packageFileNames.clear();
- this->packageFileNames.emplace_back(this->GetArchiveFileName());
- cmCPackLogger(cmCPackLog::LOG_VERBOSE,
- "Packaging all groups in one package..."
- "(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE is set)"
- << std::endl);
- DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
- Deduplicator deduplicator;
- // The ALL COMPONENTS in ONE package case
- for (auto& comp : this->Components) {
- // Add the files of this component to the archive
- this->addOneComponentToArchive(archive, &(comp.second), &deduplicator);
- }
- // archive goes out of scope so it will finalized and closed.
- return 1;
- }
- int cmCPackArchiveGenerator::PackageFiles()
- {
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Toplevel: " << this->toplevel << std::endl);
- if (this->WantsComponentInstallation()) {
- // CASE 1 : COMPONENT ALL-IN-ONE package
- // If ALL COMPONENTS in ONE package has been requested
- // then the package file is unique and should be open here.
- if (this->componentPackageMethod == ONE_PACKAGE) {
- return this->PackageComponentsAllInOne();
- }
- // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
- // There will be 1 package for each component group
- // however one may require to ignore component group and
- // in this case you'll get 1 package for each component.
- return this->PackageComponents(this->componentPackageMethod ==
- ONE_PACKAGE_PER_COMPONENT);
- }
- // CASE 3 : NON COMPONENT package.
- this->packageFileNames.clear();
- this->packageFileNames.emplace_back(this->GetArchiveFileName());
- DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
- cmWorkingDirectory workdir(this->toplevel);
- if (workdir.Failed()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR, workdir.GetError() << std::endl);
- return 0;
- }
- for (std::string const& file : this->files) {
- // Get the relative path to the file
- std::string rp = cmSystemTools::RelativePath(this->toplevel, file);
- archive.Add(rp, 0, nullptr, false);
- if (!archive) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Problem while adding file <"
- << file << "> to archive <" << this->packageFileNames[0]
- << ">, ERROR = " << archive.GetError() << std::endl);
- return 0;
- }
- }
- // The destructor of cmArchiveWrite will close and finish the write
- return 1;
- }
- int cmCPackArchiveGenerator::GenerateHeader(std::ostream* /*unused*/)
- {
- return 1;
- }
- bool cmCPackArchiveGenerator::SupportsComponentInstallation() const
- {
- // The Component installation support should only
- // be activated if explicitly requested by the user
- // (for backward compatibility reason)
- return this->IsOn("CPACK_ARCHIVE_COMPONENT_INSTALL");
- }
- int cmCPackArchiveGenerator::GetThreadCount() const
- {
- int threads = 1;
- // CPACK_ARCHIVE_THREADS overrides CPACK_THREADS
- if (cmValue v = this->GetOptionIfSet("CPACK_ARCHIVE_THREADS")) {
- threads = std::stoi(*v);
- } else if (cmValue v2 = this->GetOptionIfSet("CPACK_THREADS")) {
- threads = std::stoi(*v2);
- }
- return threads;
- }
|