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 "cmArchiveWrite.h"
6 #include "cmCPackArchiveGenerator.h"
7 #include "cmCPackLog.h"
8 #include "cmGeneratedFileStream.h"
9 #include "cmStringAlgorithms.h"
10 #include "cmSystemTools.h"
11 #include "cmWorkingDirectory.h"
13 // Needed for ::open() and ::stat()
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.
58 PkgCreate(const std::string& output_dir, const std::string& toplevel_dir,
59 const std::string& manifest_name)
61 , manifest(manifest_name)
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());
78 bool isValid() const { return d; }
84 // The API in the FreeBSD sources (the header has no documentation),
87 // int pkg_create(struct pkg_create *pc, const char *metadata, const char
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);
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).
109 const std::string& value;
111 EscapeQuotes(const std::string& s)
117 // Output a string as "string" with escaping applied.
118 cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
119 const EscapeQuotes& v)
122 for (char c : v.value) {
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.
164 ManifestKey(std::string 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
182 ManifestKeyValue(const std::string& k, std::string v)
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
198 using VList = std::vector<std::string>;
201 ManifestKeyListValue(const std::string& k)
206 ManifestKeyListValue& operator<<(const std::string& v)
212 ManifestKeyListValue& operator<<(const std::vector<std::string>& v)
214 for (std::string const& e : v) {
220 void write_value(cmGeneratedFileStream& s) const override
222 bool with_comma = false;
225 for (std::string const& elem : value) {
226 s << (with_comma ? ',' : ' ');
227 s << EscapeQuotes(elem);
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
240 ManifestKeyDepsValue(const std::string& k)
241 : ManifestKeyListValue(k)
245 void write_value(cmGeneratedFileStream& s) const override
248 for (std::string const& elem : value) {
249 s << " \"" << elem << R"(": {"origin": ")" << elem << "\"},\n";
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 << "\": ";
265 // Look up variable; if no value is set, returns an empty string;
266 // basically a wrapper that handles the NULL-ptr return from GetOption().
267 std::string cmCPackFreeBSDGenerator::var_lookup(const char* var_name)
269 cmValue pv = this->GetOption(var_name);
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 std::vector<std::string> licenses =
296 cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE"));
297 std::string licenselogic("single");
298 if (licenses.empty()) {
299 cmSystemTools::SetFatalErrorOccurred();
300 } else if (licenses.size() > 1) {
301 licenselogic = var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
303 manifest << ManifestKeyValue("licenselogic", licenselogic);
304 manifest << (ManifestKeyListValue("licenses") << licenses);
305 std::vector<std::string> categories =
306 cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES"));
307 manifest << (ManifestKeyListValue("categories") << categories);
308 manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
309 std::vector<std::string> deps =
310 cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_DEPS"));
312 manifest << (ManifestKeyDepsValue("deps") << deps);
316 // Package only actual files; others are ignored (in particular,
317 // intermediate subdirectories are ignored).
318 static bool ignore_file(const std::string& filename)
321 return stat(filename.c_str(), &statbuf) < 0 ||
322 (statbuf.st_mode & S_IFMT) != S_IFREG;
325 // Write the given list of @p files to the manifest stream @p s,
326 // as the UCL field "files" (which is dictionary-valued, to
327 // associate filenames with hashes). All the files are transformed
328 // to paths relative to @p toplevel, with a leading / (since the paths
329 // in FreeBSD package files are supposed to be absolute).
330 void write_manifest_files(cmGeneratedFileStream& s,
331 const std::string& toplevel,
332 const std::vector<std::string>& files)
334 s << "\"files\": {\n";
335 for (std::string const& file : files) {
336 s << " \"/" << cmSystemTools::RelativePath(toplevel, file) << "\": \""
337 << "<sha256>" // this gets replaced by libpkg by the actual SHA256
343 int cmCPackFreeBSDGenerator::PackageFiles()
345 if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
346 cmCPackLogger(cmCPackLog::LOG_ERROR,
347 "Error while executing CPackFreeBSD.cmake" << std::endl);
351 cmWorkingDirectory wd(toplevel);
353 files.erase(std::remove_if(files.begin(), files.end(), ignore_file),
356 std::string manifestname = toplevel + "/+MANIFEST";
358 cmGeneratedFileStream manifest(manifestname);
360 write_manifest_fields(manifest);
361 write_manifest_files(manifest, toplevel, files);
365 cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
367 if (WantsComponentInstallation()) {
368 // CASE 1 : COMPONENT ALL-IN-ONE package
369 // If ALL COMPONENTS in ONE package has been requested
370 // then the package file is unique and should be open here.
371 if (componentPackageMethod == ONE_PACKAGE) {
372 return PackageComponentsAllInOne();
374 // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
375 // There will be 1 package for each component group
376 // however one may require to ignore component group and
377 // in this case you'll get 1 package for each component.
378 return PackageComponents(componentPackageMethod ==
379 ONE_PACKAGE_PER_COMPONENT);
382 // There should be one name in the packageFileNames (already, see comment
383 // in cmCPackGenerator::DoPackage(), which holds what CPack guesses
384 // will be the package filename. libpkg does something else, though,
385 // so update the single filename to what we know will be right.
386 if (this->packageFileNames.size() == 1) {
387 std::string currentPackage = this->packageFileNames[0];
388 auto lastSlash = currentPackage.rfind('/');
390 // If there is a pathname, preserve that; libpkg will write out
391 // a file with the package name and version as specified in the
392 // manifest, so we look those up (again). lastSlash is the slash
393 // itself, we need that as path separator to the calculated package name.
394 std::string actualPackage =
395 ((lastSlash != std::string::npos)
396 ? std::string(currentPackage, 0, lastSlash + 1)
398 var_lookup("CPACK_FREEBSD_PACKAGE_NAME") + '-' +
399 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION") + FreeBSDPackageSuffix_17;
401 this->packageFileNames.clear();
402 this->packageFileNames.emplace_back(actualPackage);
405 if (!pkg_initialized() && pkg_init(NULL, NULL) != EPKG_OK) {
406 cmCPackLogger(cmCPackLog::LOG_ERROR,
407 "Can not initialize FreeBSD libpkg." << std::endl);
411 const std::string output_dir =
412 cmSystemTools::CollapseFullPath("../", toplevel);
413 PkgCreate package(output_dir, toplevel, manifestname);
414 if (package.isValid()) {
415 if (!package.Create()) {
416 cmCPackLogger(cmCPackLog::LOG_ERROR,
417 "Error during pkg_create()" << std::endl);
421 cmCPackLogger(cmCPackLog::LOG_ERROR,
422 "Error before pkg_create()" << std::endl);
426 // Specifically looking for packages suffixed with the TAG
427 std::string broken_suffix_17 =
428 cmStrCat('-', var_lookup("CPACK_TOPLEVEL_TAG"), FreeBSDPackageSuffix_17);
429 for (std::string& name : packageFileNames) {
430 cmCPackLogger(cmCPackLog::LOG_DEBUG, "Packagefile " << name << std::endl);
431 if (cmHasSuffix(name, broken_suffix_17)) {
432 name.replace(name.size() - broken_suffix_17.size(), std::string::npos,
433 FreeBSDPackageSuffix_17);
438 const std::string packageFileName =
439 var_lookup("CPACK_PACKAGE_FILE_NAME") + FreeBSDPackageSuffix_17;
440 if (packageFileNames.size() == 1 && !packageFileName.empty() &&
441 packageFileNames[0] != packageFileName) {
442 // Since libpkg always writes <name>-<version>.<suffix>,
443 // if there is a CPACK_PACKAGE_FILE_NAME set, we need to
444 // rename, and then re-set the name.
445 const std::string sourceFile = packageFileNames[0];
446 const std::string packageSubDirectory =
447 cmSystemTools::GetParentDirectory(sourceFile);
448 const std::string targetFileName =
449 packageSubDirectory + '/' + packageFileName;
450 if (cmSystemTools::RenameFile(sourceFile, targetFileName)) {
451 this->packageFileNames.clear();
452 this->packageFileNames.emplace_back(targetFileName);