CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / CPack / cmCPackFreeBSDGenerator.cxx
blob6beb644c83874a9f578bfd0ae4c4580a38ffd495
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 "cmCPackFreeBSDGenerator.h"
5 #include <algorithm>
6 #include <ostream>
7 #include <utility>
8 #include <vector>
10 #include <fcntl.h>
11 #include <pkg.h>
13 #include <sys/stat.h>
15 #include "cmArchiveWrite.h"
16 #include "cmCPackArchiveGenerator.h"
17 #include "cmCPackLog.h"
18 #include "cmGeneratedFileStream.h"
19 #include "cmList.h"
20 #include "cmStringAlgorithms.h"
21 #include "cmSystemTools.h"
22 #include "cmWorkingDirectory.h"
24 // Suffix used to tell libpkg what compression to use
25 static const char FreeBSDPackageCompression[] = "txz";
26 static const char FreeBSDPackageSuffix_17[] = ".pkg";
28 cmCPackFreeBSDGenerator::cmCPackFreeBSDGenerator()
29 : cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr",
30 FreeBSDPackageSuffix_17)
34 int cmCPackFreeBSDGenerator::InitializeInternal()
36 this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr/local");
37 this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "0");
38 return this->Superclass::InitializeInternal();
41 cmCPackFreeBSDGenerator::~cmCPackFreeBSDGenerator() = default;
43 // This is a wrapper for struct pkg_create and pkg_create()
45 // Instantiate this class with suitable parameters, then
46 // check isValid() to check if it's ok. Afterwards, call
47 // Create() to do the actual work. This will leave a package
48 // in the given `output_dir`.
50 // This wrapper cleans up the struct pkg_create.
51 class PkgCreate
53 public:
54 PkgCreate()
55 : d(nullptr)
58 PkgCreate(const std::string& output_dir, const std::string& toplevel_dir,
59 const std::string& manifest_name)
60 : d(pkg_create_new())
61 , manifest(manifest_name)
64 if (d) {
65 pkg_create_set_format(d, FreeBSDPackageCompression);
66 pkg_create_set_compression_level(d, 0); // Explicitly set default
67 pkg_create_set_overwrite(d, false);
68 pkg_create_set_rootdir(d, toplevel_dir.c_str());
69 pkg_create_set_output_dir(d, output_dir.c_str());
72 ~PkgCreate()
74 if (d)
75 pkg_create_free(d);
78 bool isValid() const { return d; }
80 bool Create()
82 if (!isValid())
83 return false;
84 // The API in the FreeBSD sources (the header has no documentation),
85 // is as follows:
87 // int pkg_create(struct pkg_create *pc, const char *metadata, const char
88 // *plist, bool hash)
90 // We let the plist be determined from what is installed, and all
91 // the rest comes from the manifest data.
92 int r = pkg_create(d, manifest.c_str(), nullptr, false);
93 return r == 0;
96 private:
97 struct pkg_create* d;
98 std::string manifest;
101 // This is a wrapper, for use only in stream-based output,
102 // that will output a string in UCL escaped fashion (in particular,
103 // quotes and backslashes are escaped). The list of characters
104 // to escape is taken from https://github.com/vstakhov/libucl
105 // (which is the reference implementation pkg(8) refers to).
106 class EscapeQuotes
108 public:
109 const std::string& value;
111 EscapeQuotes(const std::string& s)
112 : value(s)
117 // Output a string as "string" with escaping applied.
118 cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
119 const EscapeQuotes& v)
121 s << '"';
122 for (char c : v.value) {
123 switch (c) {
124 case '\n':
125 s << "\\n";
126 break;
127 case '\r':
128 s << "\\r";
129 break;
130 case '\b':
131 s << "\\b";
132 break;
133 case '\t':
134 s << "\\t";
135 break;
136 case '\f':
137 s << "\\f";
138 break;
139 case '\\':
140 s << "\\\\";
141 break;
142 case '"':
143 s << "\\\"";
144 break;
145 default:
146 s << c;
147 break;
150 s << '"';
151 return s;
154 // The following classes are all helpers for writing out the UCL
155 // manifest file (it also looks like JSON). ManifestKey just has
156 // a (string-valued) key; subclasses add a specific kind of
157 // value-type to the key, and implement write_value() to output
158 // the corresponding UCL.
159 class ManifestKey
161 public:
162 std::string key;
164 ManifestKey(std::string k)
165 : key(std::move(k))
169 virtual ~ManifestKey() = default;
171 // Output the value associated with this key to the stream @p s.
172 // Format is to be decided by subclasses.
173 virtual void write_value(cmGeneratedFileStream& s) const = 0;
176 // Basic string-value (e.g. "name": "cmake")
177 class ManifestKeyValue : public ManifestKey
179 public:
180 std::string value;
182 ManifestKeyValue(const std::string& k, std::string v)
183 : ManifestKey(k)
184 , value(std::move(v))
188 void write_value(cmGeneratedFileStream& s) const override
190 s << EscapeQuotes(value);
194 // List-of-strings values (e.g. "licenses": ["GPLv2", "LGPLv2"])
195 class ManifestKeyListValue : public ManifestKey
197 public:
198 using VList = std::vector<std::string>;
199 VList value;
201 ManifestKeyListValue(const std::string& k)
202 : ManifestKey(k)
206 ManifestKeyListValue& operator<<(const std::string& v)
208 value.push_back(v);
209 return *this;
212 ManifestKeyListValue& operator<<(const std::vector<std::string>& v)
214 for (std::string const& e : v) {
215 (*this) << e;
217 return *this;
220 void write_value(cmGeneratedFileStream& s) const override
222 bool with_comma = false;
224 s << '[';
225 for (std::string const& elem : value) {
226 s << (with_comma ? ',' : ' ');
227 s << EscapeQuotes(elem);
228 with_comma = true;
230 s << " ]";
234 // Deps: actually a dictionary, but we'll treat it as a
235 // list so we only name the deps, and produce dictionary-
236 // like output via write_value()
237 class ManifestKeyDepsValue : public ManifestKeyListValue
239 public:
240 ManifestKeyDepsValue(const std::string& k)
241 : ManifestKeyListValue(k)
245 void write_value(cmGeneratedFileStream& s) const override
247 s << "{\n";
248 for (std::string const& elem : value) {
249 s << " \"" << elem << R"(": {"origin": ")" << elem << "\"},\n";
251 s << '}';
255 // Write one of the key-value classes (above) to the stream @p s
256 cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
257 const ManifestKey& v)
259 s << '"' << v.key << "\": ";
260 v.write_value(s);
261 s << ",\n";
262 return s;
265 // Look up variable; if no value is set, returns an empty string;
266 // basically a wrapper that handles the nullptr return from GetOption().
267 std::string cmCPackFreeBSDGenerator::var_lookup(const char* var_name)
269 cmValue pv = this->GetOption(var_name);
270 if (!pv) {
271 return {};
273 return *pv;
276 // Produce UCL in the given @p manifest file for the common
277 // manifest fields (common to the compact and regular formats),
278 // by reading the CPACK_FREEBSD_* variables.
279 void cmCPackFreeBSDGenerator::write_manifest_fields(
280 cmGeneratedFileStream& manifest)
282 manifest << ManifestKeyValue("name",
283 var_lookup("CPACK_FREEBSD_PACKAGE_NAME"));
284 manifest << ManifestKeyValue("origin",
285 var_lookup("CPACK_FREEBSD_PACKAGE_ORIGIN"));
286 manifest << ManifestKeyValue("version",
287 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION"));
288 manifest << ManifestKeyValue("maintainer",
289 var_lookup("CPACK_FREEBSD_PACKAGE_MAINTAINER"));
290 manifest << ManifestKeyValue("comment",
291 var_lookup("CPACK_FREEBSD_PACKAGE_COMMENT"));
292 manifest << ManifestKeyValue(
293 "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
294 manifest << ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
295 cmList licenses{ var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE") };
296 std::string licenselogic("single");
297 if (licenses.empty()) {
298 cmSystemTools::SetFatalErrorOccurred();
299 } else if (licenses.size() > 1) {
300 licenselogic = var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
302 manifest << ManifestKeyValue("licenselogic", licenselogic);
303 manifest << (ManifestKeyListValue("licenses") << licenses);
304 cmList categories{ var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES") };
305 manifest << (ManifestKeyListValue("categories") << categories);
306 manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
307 cmList deps{ var_lookup("CPACK_FREEBSD_PACKAGE_DEPS") };
308 if (!deps.empty()) {
309 manifest << (ManifestKeyDepsValue("deps") << deps);
313 // Package only actual files; others are ignored (in particular,
314 // intermediate subdirectories are ignored).
315 static bool ignore_file(const std::string& filename)
317 struct stat statbuf;
318 return stat(filename.c_str(), &statbuf) < 0 ||
319 (statbuf.st_mode & S_IFMT) != S_IFREG;
322 // Write the given list of @p files to the manifest stream @p s,
323 // as the UCL field "files" (which is dictionary-valued, to
324 // associate filenames with hashes). All the files are transformed
325 // to paths relative to @p toplevel, with a leading / (since the paths
326 // in FreeBSD package files are supposed to be absolute).
327 void write_manifest_files(cmGeneratedFileStream& s,
328 const std::string& toplevel,
329 const std::vector<std::string>& files)
331 s << "\"files\": {\n";
332 for (std::string const& file : files) {
333 s << " \"/" << cmSystemTools::RelativePath(toplevel, file) << "\": \""
334 << "<sha256>" // this gets replaced by libpkg by the actual SHA256
335 << "\",\n";
337 s << " },\n";
340 int cmCPackFreeBSDGenerator::PackageFiles()
342 if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
343 cmCPackLogger(cmCPackLog::LOG_ERROR,
344 "Error while executing CPackFreeBSD.cmake" << std::endl);
345 return 0;
348 cmWorkingDirectory wd(toplevel);
350 files.erase(std::remove_if(files.begin(), files.end(), ignore_file),
351 files.end());
353 std::string manifestname = toplevel + "/+MANIFEST";
355 cmGeneratedFileStream manifest(manifestname);
356 manifest << "{\n";
357 write_manifest_fields(manifest);
358 write_manifest_files(manifest, toplevel, files);
359 manifest << "}\n";
362 cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
364 if (WantsComponentInstallation()) {
365 // CASE 1 : COMPONENT ALL-IN-ONE package
366 // If ALL COMPONENTS in ONE package has been requested
367 // then the package file is unique and should be open here.
368 if (componentPackageMethod == ONE_PACKAGE) {
369 return PackageComponentsAllInOne();
371 // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
372 // There will be 1 package for each component group
373 // however one may require to ignore component group and
374 // in this case you'll get 1 package for each component.
375 return PackageComponents(componentPackageMethod ==
376 ONE_PACKAGE_PER_COMPONENT);
379 // There should be one name in the packageFileNames (already, see comment
380 // in cmCPackGenerator::DoPackage(), which holds what CPack guesses
381 // will be the package filename. libpkg does something else, though,
382 // so update the single filename to what we know will be right.
383 if (this->packageFileNames.size() == 1) {
384 std::string currentPackage = this->packageFileNames[0];
385 auto lastSlash = currentPackage.rfind('/');
387 // If there is a pathname, preserve that; libpkg will write out
388 // a file with the package name and version as specified in the
389 // manifest, so we look those up (again). lastSlash is the slash
390 // itself, we need that as path separator to the calculated package name.
391 std::string actualPackage =
392 ((lastSlash != std::string::npos)
393 ? std::string(currentPackage, 0, lastSlash + 1)
394 : std::string()) +
395 var_lookup("CPACK_FREEBSD_PACKAGE_NAME") + '-' +
396 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION") + FreeBSDPackageSuffix_17;
398 this->packageFileNames.clear();
399 this->packageFileNames.emplace_back(actualPackage);
402 if (!pkg_initialized() && pkg_init(nullptr, nullptr) != EPKG_OK) {
403 cmCPackLogger(cmCPackLog::LOG_ERROR,
404 "Can not initialize FreeBSD libpkg." << std::endl);
405 return 0;
408 const std::string output_dir = cmSystemTools::GetFilenamePath(toplevel);
409 PkgCreate package(output_dir, toplevel, manifestname);
410 if (package.isValid()) {
411 if (!package.Create()) {
412 cmCPackLogger(cmCPackLog::LOG_ERROR,
413 "Error during pkg_create()" << std::endl);
414 return 0;
416 } else {
417 cmCPackLogger(cmCPackLog::LOG_ERROR,
418 "Error before pkg_create()" << std::endl);
419 return 0;
422 // Specifically looking for packages suffixed with the TAG
423 std::string broken_suffix_17 =
424 cmStrCat('-', var_lookup("CPACK_TOPLEVEL_TAG"), FreeBSDPackageSuffix_17);
425 for (std::string& name : packageFileNames) {
426 cmCPackLogger(cmCPackLog::LOG_DEBUG, "Packagefile " << name << std::endl);
427 if (cmHasSuffix(name, broken_suffix_17)) {
428 name.replace(name.size() - broken_suffix_17.size(), std::string::npos,
429 FreeBSDPackageSuffix_17);
430 break;
434 const std::string packageFileName =
435 var_lookup("CPACK_PACKAGE_FILE_NAME") + FreeBSDPackageSuffix_17;
436 if (packageFileNames.size() == 1 && !packageFileName.empty() &&
437 packageFileNames[0] != packageFileName) {
438 // Since libpkg always writes <name>-<version>.<suffix>,
439 // if there is a CPACK_PACKAGE_FILE_NAME set, we need to
440 // rename, and then re-set the name.
441 const std::string sourceFile = packageFileNames[0];
442 const std::string packageSubDirectory =
443 cmSystemTools::GetParentDirectory(sourceFile);
444 const std::string targetFileName =
445 packageSubDirectory + '/' + packageFileName;
446 if (cmSystemTools::RenameFile(sourceFile, targetFileName)) {
447 this->packageFileNames.clear();
448 this->packageFileNames.emplace_back(targetFileName);
452 return 1;