Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / CPack / cmCPackArchiveGenerator.cxx
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
5 #include <cstring>
6 #include <map>
7 #include <ostream>
8 #include <utility>
9 #include <vector>
10
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
20 cmCPackGenerator* cmCPackArchiveGenerator::Create7ZGenerator()
21 {
22   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "7zip",
23                                      ".7z");
24 }
25
26 cmCPackGenerator* cmCPackArchiveGenerator::CreateTBZ2Generator()
27 {
28   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressBZip2, "paxr",
29                                      ".tar.bz2");
30 }
31
32 cmCPackGenerator* cmCPackArchiveGenerator::CreateTGZGenerator()
33 {
34   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressGZip, "paxr",
35                                      ".tar.gz");
36 }
37
38 cmCPackGenerator* cmCPackArchiveGenerator::CreateTXZGenerator()
39 {
40   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr",
41                                      ".tar.xz");
42 }
43
44 cmCPackGenerator* cmCPackArchiveGenerator::CreateTZGenerator()
45 {
46   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressCompress, "paxr",
47                                      ".tar.Z");
48 }
49
50 cmCPackGenerator* cmCPackArchiveGenerator::CreateTZSTGenerator()
51 {
52   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressZstd, "paxr",
53                                      ".tar.zst");
54 }
55
56 cmCPackGenerator* cmCPackArchiveGenerator::CreateZIPGenerator()
57 {
58   return new cmCPackArchiveGenerator(cmArchiveWrite::CompressNone, "zip",
59                                      ".zip");
60 }
61
62 cmCPackArchiveGenerator::cmCPackArchiveGenerator(
63   cmArchiveWrite::Compress compress, std::string format, std::string extension)
64   : Compress(compress)
65   , ArchiveFormat(std::move(format))
66   , OutputExtension(std::move(extension))
67 {
68 }
69
70 cmCPackArchiveGenerator::~cmCPackArchiveGenerator() = default;
71
72 std::string cmCPackArchiveGenerator::GetArchiveComponentFileName(
73   const std::string& component, bool isGroupName)
74 {
75   std::string componentUpper(cmSystemTools::UpperCase(component));
76   std::string packageFileName;
77
78   if (this->IsSet("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME")) {
79     packageFileName +=
80       *this->GetOption("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME");
81   } else if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) {
82     packageFileName += this->GetComponentPackageFileName(
83       this->GetOption("CPACK_ARCHIVE_FILE_NAME"), component, isGroupName);
84   } else {
85     packageFileName += this->GetComponentPackageFileName(
86       this->GetOption("CPACK_PACKAGE_FILE_NAME"), component, isGroupName);
87   }
88
89   packageFileName += this->GetOutputExtension();
90
91   return packageFileName;
92 }
93
94 int cmCPackArchiveGenerator::InitializeInternal()
95 {
96   this->SetOptionIfNotSet("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "1");
97   cmValue newExtensionValue = this->GetOption("CPACK_ARCHIVE_FILE_EXTENSION");
98   if (!newExtensionValue.IsEmpty()) {
99     std::string newExtension = *newExtensionValue;
100     if (!cmHasLiteralPrefix(newExtension, ".")) {
101       newExtension = cmStrCat('.', newExtension);
102     }
103     cmCPackLogger(cmCPackLog::LOG_DEBUG,
104                   "Using user-provided file extension "
105                     << newExtension << " instead of the default "
106                     << this->OutputExtension << std::endl);
107     this->OutputExtension = std::move(newExtension);
108   }
109   return this->Superclass::InitializeInternal();
110 }
111
112 int cmCPackArchiveGenerator::addOneComponentToArchive(
113   cmArchiveWrite& archive, cmCPackComponent* component)
114 {
115   cmCPackLogger(cmCPackLog::LOG_VERBOSE,
116                 "   - packaging component: " << component->Name << std::endl);
117   // Add the files of this component to the archive
118   std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
119   localToplevel += "/" + component->Name;
120   // Change to local toplevel
121   cmWorkingDirectory workdir(localToplevel);
122   if (workdir.Failed()) {
123     cmCPackLogger(cmCPackLog::LOG_ERROR,
124                   "Failed to change working directory to "
125                     << localToplevel << " : "
126                     << std::strerror(workdir.GetLastResult()) << std::endl);
127     return 0;
128   }
129   std::string filePrefix;
130   if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY")) {
131     filePrefix = cmStrCat(this->GetOption("CPACK_PACKAGE_FILE_NAME"), '/');
132   }
133   cmValue installPrefix = this->GetOption("CPACK_PACKAGING_INSTALL_PREFIX");
134   if (installPrefix && installPrefix->size() > 1 &&
135       (*installPrefix)[0] == '/') {
136     // add to file prefix and remove the leading '/'
137     filePrefix += installPrefix->substr(1);
138     filePrefix += "/";
139   }
140   for (std::string const& file : component->Files) {
141     std::string rp = filePrefix + file;
142     cmCPackLogger(cmCPackLog::LOG_DEBUG, "Adding file: " << rp << std::endl);
143     archive.Add(rp, 0, nullptr, false);
144     if (!archive) {
145       cmCPackLogger(cmCPackLog::LOG_ERROR,
146                     "ERROR while packaging files: " << archive.GetError()
147                                                     << std::endl);
148       return 0;
149     }
150   }
151   return 1;
152 }
153
154 /*
155  * The macro will open/create a file 'filename'
156  * an declare and open the associated
157  * cmArchiveWrite 'archive' object.
158  */
159 #define DECLARE_AND_OPEN_ARCHIVE(filename, archive)                           \
160   cmGeneratedFileStream gf;                                                   \
161   gf.Open((filename), false, true);                                           \
162   if (!GenerateHeader(&gf)) {                                                 \
163     cmCPackLogger(cmCPackLog::LOG_ERROR,                                      \
164                   "Problem to generate Header for archive <"                  \
165                     << (filename) << ">." << std::endl);                      \
166     return 0;                                                                 \
167   }                                                                           \
168   cmArchiveWrite archive(gf, this->Compress, this->ArchiveFormat, 0,          \
169                          this->GetThreadCount());                             \
170   do {                                                                        \
171     if (!archive.Open()) {                                                    \
172       cmCPackLogger(cmCPackLog::LOG_ERROR,                                    \
173                     "Problem to open archive <"                               \
174                       << (filename) << ">, ERROR = " << (archive).GetError()  \
175                       << std::endl);                                          \
176       return 0;                                                               \
177     }                                                                         \
178     if (!(archive)) {                                                         \
179       cmCPackLogger(cmCPackLog::LOG_ERROR,                                    \
180                     "Problem to create archive <"                             \
181                       << (filename) << ">, ERROR = " << (archive).GetError()  \
182                       << std::endl);                                          \
183       return 0;                                                               \
184     }                                                                         \
185   } while (false)
186
187 int cmCPackArchiveGenerator::PackageComponents(bool ignoreGroup)
188 {
189   this->packageFileNames.clear();
190   // The default behavior is to have one package by component group
191   // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
192   if (!ignoreGroup) {
193     for (auto const& compG : this->ComponentGroups) {
194       cmCPackLogger(cmCPackLog::LOG_VERBOSE,
195                     "Packaging component group: " << compG.first << std::endl);
196       // Begin the archive for this group
197       std::string packageFileName = std::string(this->toplevel) + "/" +
198         this->GetArchiveComponentFileName(compG.first, true);
199
200       // open a block in order to automatically close archive
201       // at the end of the block
202       {
203         DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
204         // now iterate over the component of this group
205         for (cmCPackComponent* comp : (compG.second).Components) {
206           // Add the files of this component to the archive
207           this->addOneComponentToArchive(archive, comp);
208         }
209       }
210       // add the generated package to package file names list
211       this->packageFileNames.push_back(std::move(packageFileName));
212     }
213     // Handle Orphan components (components not belonging to any groups)
214     for (auto& comp : this->Components) {
215       // Does the component belong to a group?
216       if (comp.second.Group == nullptr) {
217         cmCPackLogger(
218           cmCPackLog::LOG_VERBOSE,
219           "Component <"
220             << comp.second.Name
221             << "> does not belong to any group, package it separately."
222             << std::endl);
223         std::string localToplevel(
224           this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
225         std::string packageFileName = std::string(this->toplevel);
226
227         localToplevel += "/" + comp.first;
228         packageFileName +=
229           "/" + this->GetArchiveComponentFileName(comp.first, false);
230
231         {
232           DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
233           // Add the files of this component to the archive
234           this->addOneComponentToArchive(archive, &(comp.second));
235         }
236         // add the generated package to package file names list
237         this->packageFileNames.push_back(std::move(packageFileName));
238       }
239     }
240   }
241   // CPACK_COMPONENTS_IGNORE_GROUPS is set
242   // We build 1 package per component
243   else {
244     for (auto& comp : this->Components) {
245       std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
246       std::string packageFileName = std::string(this->toplevel);
247
248       localToplevel += "/" + comp.first;
249       packageFileName +=
250         "/" + this->GetArchiveComponentFileName(comp.first, false);
251
252       {
253         DECLARE_AND_OPEN_ARCHIVE(packageFileName, archive);
254         // Add the files of this component to the archive
255         this->addOneComponentToArchive(archive, &(comp.second));
256       }
257       // add the generated package to package file names list
258       this->packageFileNames.push_back(std::move(packageFileName));
259     }
260   }
261   return 1;
262 }
263
264 int cmCPackArchiveGenerator::PackageComponentsAllInOne()
265 {
266   // reset the package file names
267   this->packageFileNames.clear();
268   this->packageFileNames.emplace_back(this->toplevel);
269   this->packageFileNames[0] += "/";
270
271   if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) {
272     this->packageFileNames[0] += *this->GetOption("CPACK_ARCHIVE_FILE_NAME");
273   } else {
274     this->packageFileNames[0] += *this->GetOption("CPACK_PACKAGE_FILE_NAME");
275   }
276
277   this->packageFileNames[0] += this->GetOutputExtension();
278
279   cmCPackLogger(cmCPackLog::LOG_VERBOSE,
280                 "Packaging all groups in one package..."
281                 "(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE is set)"
282                   << std::endl);
283   DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
284
285   // The ALL COMPONENTS in ONE package case
286   for (auto& comp : this->Components) {
287     // Add the files of this component to the archive
288     this->addOneComponentToArchive(archive, &(comp.second));
289   }
290
291   // archive goes out of scope so it will finalized and closed.
292   return 1;
293 }
294
295 int cmCPackArchiveGenerator::PackageFiles()
296 {
297   cmCPackLogger(cmCPackLog::LOG_DEBUG,
298                 "Toplevel: " << this->toplevel << std::endl);
299
300   if (this->WantsComponentInstallation()) {
301     // CASE 1 : COMPONENT ALL-IN-ONE package
302     // If ALL COMPONENTS in ONE package has been requested
303     // then the package file is unique and should be open here.
304     if (this->componentPackageMethod == ONE_PACKAGE) {
305       return this->PackageComponentsAllInOne();
306     }
307     // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
308     // There will be 1 package for each component group
309     // however one may require to ignore component group and
310     // in this case you'll get 1 package for each component.
311     return this->PackageComponents(this->componentPackageMethod ==
312                                    ONE_PACKAGE_PER_COMPONENT);
313   }
314
315   // CASE 3 : NON COMPONENT package.
316   DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
317   cmWorkingDirectory workdir(this->toplevel);
318   if (workdir.Failed()) {
319     cmCPackLogger(cmCPackLog::LOG_ERROR,
320                   "Failed to change working directory to "
321                     << this->toplevel << " : "
322                     << std::strerror(workdir.GetLastResult()) << std::endl);
323     return 0;
324   }
325   for (std::string const& file : this->files) {
326     // Get the relative path to the file
327     std::string rp = cmSystemTools::RelativePath(this->toplevel, file);
328     archive.Add(rp, 0, nullptr, false);
329     if (!archive) {
330       cmCPackLogger(cmCPackLog::LOG_ERROR,
331                     "Problem while adding file <"
332                       << file << "> to archive <" << this->packageFileNames[0]
333                       << ">, ERROR = " << archive.GetError() << std::endl);
334       return 0;
335     }
336   }
337   // The destructor of cmArchiveWrite will close and finish the write
338   return 1;
339 }
340
341 int cmCPackArchiveGenerator::GenerateHeader(std::ostream* /*unused*/)
342 {
343   return 1;
344 }
345
346 bool cmCPackArchiveGenerator::SupportsComponentInstallation() const
347 {
348   // The Component installation support should only
349   // be activated if explicitly requested by the user
350   // (for backward compatibility reason)
351   return this->IsOn("CPACK_ARCHIVE_COMPONENT_INSTALL");
352 }
353
354 int cmCPackArchiveGenerator::GetThreadCount() const
355 {
356   int threads = 1;
357
358   // CPACK_ARCHIVE_THREADS overrides CPACK_THREADS
359   if (this->IsSet("CPACK_ARCHIVE_THREADS")) {
360     threads = std::stoi(this->GetOption("CPACK_ARCHIVE_THREADS"));
361   } else if (this->IsSet("CPACK_THREADS")) {
362     threads = std::stoi(this->GetOption("CPACK_THREADS"));
363   }
364
365   return threads;
366 }