/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCPackArchiveGenerator.h" #include #include #include #include #include #include #include "cmCPackComponentGroup.h" #include "cmCPackGenerator.h" #include "cmCPackLog.h" #include "cmGeneratedFileStream.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmWorkingDirectory.h" 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::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::GetArchiveComponentFileName( const std::string& component, bool isGroupName) { std::string componentUpper(cmSystemTools::UpperCase(component)); std::string packageFileName; if (this->IsSet("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME")) { packageFileName += this->GetOption("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME"); } else if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) { packageFileName += this->GetComponentPackageFileName( this->GetOption("CPACK_ARCHIVE_FILE_NAME"), component, isGroupName); } else { packageFileName += this->GetComponentPackageFileName( this->GetOption("CPACK_PACKAGE_FILE_NAME"), component, isGroupName); } packageFileName += this->GetOutputExtension(); return packageFileName; } int cmCPackArchiveGenerator::InitializeInternal() { this->SetOptionIfNotSet("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "1"); return this->Superclass::InitializeInternal(); } int cmCPackArchiveGenerator::addOneComponentToArchive( cmArchiveWrite& archive, cmCPackComponent* component) { 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 += "/" + component->Name; // Change to local toplevel cmWorkingDirectory workdir(localToplevel); if (workdir.Failed()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Failed to change working directory to " << localToplevel << " : " << std::strerror(workdir.GetLastResult()) << std::endl); return 0; } std::string filePrefix; if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY")) { filePrefix = cmStrCat(this->GetOption("CPACK_PACKAGE_FILE_NAME"), '/'); } const char* installPrefix = this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX"); if (installPrefix && installPrefix[0] == '/' && installPrefix[1] != 0) { // add to file prefix and remove the leading '/' filePrefix += installPrefix + 1; filePrefix += "/"; } for (std::string const& file : component->Files) { std::string rp = filePrefix + file; cmCPackLogger(cmCPackLog::LOG_DEBUG, "Adding file: " << rp << std::endl); archive.Add(rp, 0, nullptr, false); 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); \ do { \ if (!this->SetArchiveOptions(&archive)) { \ cmCPackLogger(cmCPackLog::LOG_ERROR, \ "Problem to set archive options <" \ << (filename) << ">, ERROR = " << (archive).GetError() \ << std::endl); \ return 0; \ } \ 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); // 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); } } // 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 == nullptr) { cmCPackLogger( cmCPackLog::LOG_VERBOSE, "Component <" << comp.second.Name << "> does not belong to any group, package it separately." << std::endl); std::string localToplevel( this->GetOption("CPACK_TEMPORARY_DIRECTORY")); std::string packageFileName = std::string(this->toplevel); localToplevel += "/" + comp.first; 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)); } // 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 localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY")); std::string packageFileName = std::string(this->toplevel); localToplevel += "/" + comp.first; 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)); } // 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->toplevel); this->packageFileNames[0] += "/"; if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) { this->packageFileNames[0] += this->GetOption("CPACK_ARCHIVE_FILE_NAME"); } else { this->packageFileNames[0] += this->GetOption("CPACK_PACKAGE_FILE_NAME"); } this->packageFileNames[0] += this->GetOutputExtension(); 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); // 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)); } // 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. DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive); cmWorkingDirectory workdir(this->toplevel); if (workdir.Failed()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Failed to change working directory to " << this->toplevel << " : " << std::strerror(workdir.GetLastResult()) << 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"); } bool cmCPackArchiveGenerator::SetArchiveOptions(cmArchiveWrite* archive) { #if ARCHIVE_VERSION_NUMBER >= 3004000 // Upstream fixed an issue with their integer parsing in 3.4.0 which would // cause spurious errors to be raised from `strtoull`. if (this->Compress == cmArchiveWrite::CompressXZ) { const char* threads = "1"; if (this->IsSet("CPACK_ARCHIVE_THREADS")) { threads = this->GetOption("CPACK_ARCHIVE_THREADS"); } if (!archive->SetFilterOption("xz", "threads", threads)) { return false; } } #endif return true; }