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 // Resulting package file-suffix, for < 1.17 and >= 1.17 versions of libpkg
27 static const char FreeBSDPackageSuffix_10[] = ".txz";
28 static const char FreeBSDPackageSuffix_17[] = ".pkg";
30 cmCPackFreeBSDGenerator::cmCPackFreeBSDGenerator()
31 : cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr",
32 FreeBSDPackageSuffix_17)
36 int cmCPackFreeBSDGenerator::InitializeInternal()
38 this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr/local");
39 this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "0");
40 return this->Superclass::InitializeInternal();
43 cmCPackFreeBSDGenerator::~cmCPackFreeBSDGenerator() = default;
45 // This is a wrapper for struct pkg_create and pkg_create()
47 // Instantiate this class with suitable parameters, then
48 // check isValid() to check if it's ok. Afterwards, call
49 // Create() to do the actual work. This will leave a package
50 // in the given `output_dir`.
52 // This wrapper cleans up the struct pkg_create.
60 PkgCreate(const std::string& output_dir, const std::string& toplevel_dir,
61 const std::string& manifest_name)
63 , manifest(manifest_name)
67 pkg_create_set_format(d, FreeBSDPackageCompression);
68 pkg_create_set_compression_level(d, 0); // Explicitly set default
69 pkg_create_set_overwrite(d, false);
70 pkg_create_set_rootdir(d, toplevel_dir.c_str());
71 pkg_create_set_output_dir(d, output_dir.c_str());
80 bool isValid() const { return d; }
86 int r = pkg_create(d, manifest.c_str(), nullptr, false);
95 // This is a wrapper, for use only in stream-based output,
96 // that will output a string in UCL escaped fashion (in particular,
97 // quotes and backslashes are escaped). The list of characters
98 // to escape is taken from https://github.com/vstakhov/libucl
99 // (which is the reference implementation pkg(8) refers to).
103 const std::string& value;
105 EscapeQuotes(const std::string& s)
111 // Output a string as "string" with escaping applied.
112 cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
113 const EscapeQuotes& v)
116 for (char c : v.value) {
148 // The following classes are all helpers for writing out the UCL
149 // manifest file (it also looks like JSON). ManifestKey just has
150 // a (string-valued) key; subclasses add a specific kind of
151 // value-type to the key, and implement write_value() to output
152 // the corresponding UCL.
158 ManifestKey(std::string k)
163 virtual ~ManifestKey() = default;
165 // Output the value associated with this key to the stream @p s.
166 // Format is to be decided by subclasses.
167 virtual void write_value(cmGeneratedFileStream& s) const = 0;
170 // Basic string-value (e.g. "name": "cmake")
171 class ManifestKeyValue : public ManifestKey
176 ManifestKeyValue(const std::string& k, std::string v)
178 , value(std::move(v))
182 void write_value(cmGeneratedFileStream& s) const override
184 s << EscapeQuotes(value);
188 // List-of-strings values (e.g. "licenses": ["GPLv2", "LGPLv2"])
189 class ManifestKeyListValue : public ManifestKey
192 using VList = std::vector<std::string>;
195 ManifestKeyListValue(const std::string& k)
200 ManifestKeyListValue& operator<<(const std::string& v)
206 ManifestKeyListValue& operator<<(const std::vector<std::string>& v)
208 for (std::string const& e : v) {
214 void write_value(cmGeneratedFileStream& s) const override
216 bool with_comma = false;
219 for (std::string const& elem : value) {
220 s << (with_comma ? ',' : ' ');
221 s << EscapeQuotes(elem);
228 // Deps: actually a dictionary, but we'll treat it as a
229 // list so we only name the deps, and produce dictionary-
230 // like output via write_value()
231 class ManifestKeyDepsValue : public ManifestKeyListValue
234 ManifestKeyDepsValue(const std::string& k)
235 : ManifestKeyListValue(k)
239 void write_value(cmGeneratedFileStream& s) const override
242 for (std::string const& elem : value) {
243 s << " \"" << elem << R"(": {"origin": ")" << elem << "\"},\n";
249 // Write one of the key-value classes (above) to the stream @p s
250 cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
251 const ManifestKey& v)
253 s << '"' << v.key << "\": ";
259 // Look up variable; if no value is set, returns an empty string;
260 // basically a wrapper that handles the NULL-ptr return from GetOption().
261 std::string cmCPackFreeBSDGenerator::var_lookup(const char* var_name)
263 cmValue pv = this->GetOption(var_name);
270 // Produce UCL in the given @p manifest file for the common
271 // manifest fields (common to the compact and regular formats),
272 // by reading the CPACK_FREEBSD_* variables.
273 void cmCPackFreeBSDGenerator::write_manifest_fields(
274 cmGeneratedFileStream& manifest)
276 manifest << ManifestKeyValue("name",
277 var_lookup("CPACK_FREEBSD_PACKAGE_NAME"));
278 manifest << ManifestKeyValue("origin",
279 var_lookup("CPACK_FREEBSD_PACKAGE_ORIGIN"));
280 manifest << ManifestKeyValue("version",
281 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION"));
282 manifest << ManifestKeyValue("maintainer",
283 var_lookup("CPACK_FREEBSD_PACKAGE_MAINTAINER"));
284 manifest << ManifestKeyValue("comment",
285 var_lookup("CPACK_FREEBSD_PACKAGE_COMMENT"));
286 manifest << ManifestKeyValue(
287 "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
288 manifest << ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
289 std::vector<std::string> licenses =
290 cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE"));
291 std::string licenselogic("single");
292 if (licenses.empty()) {
293 cmSystemTools::SetFatalErrorOccurred();
294 } else if (licenses.size() > 1) {
295 licenselogic = var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
297 manifest << ManifestKeyValue("licenselogic", licenselogic);
298 manifest << (ManifestKeyListValue("licenses") << licenses);
299 std::vector<std::string> categories =
300 cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES"));
301 manifest << (ManifestKeyListValue("categories") << categories);
302 manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
303 std::vector<std::string> deps =
304 cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_DEPS"));
306 manifest << (ManifestKeyDepsValue("deps") << deps);
310 // Package only actual files; others are ignored (in particular,
311 // intermediate subdirectories are ignored).
312 static bool ignore_file(const std::string& filename)
315 return stat(filename.c_str(), &statbuf) < 0 ||
316 (statbuf.st_mode & S_IFMT) != S_IFREG;
319 // Write the given list of @p files to the manifest stream @p s,
320 // as the UCL field "files" (which is dictionary-valued, to
321 // associate filenames with hashes). All the files are transformed
322 // to paths relative to @p toplevel, with a leading / (since the paths
323 // in FreeBSD package files are supposed to be absolute).
324 void write_manifest_files(cmGeneratedFileStream& s,
325 const std::string& toplevel,
326 const std::vector<std::string>& files)
328 s << "\"files\": {\n";
329 for (std::string const& file : files) {
330 s << " \"/" << cmSystemTools::RelativePath(toplevel, file) << "\": \""
331 << "<sha256>" // this gets replaced by libpkg by the actual SHA256
337 int cmCPackFreeBSDGenerator::PackageFiles()
339 if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
340 cmCPackLogger(cmCPackLog::LOG_ERROR,
341 "Error while executing CPackFreeBSD.cmake" << std::endl);
345 cmWorkingDirectory wd(toplevel);
347 files.erase(std::remove_if(files.begin(), files.end(), ignore_file),
350 std::string manifestname = toplevel + "/+MANIFEST";
352 cmGeneratedFileStream manifest(manifestname);
354 write_manifest_fields(manifest);
355 write_manifest_files(manifest, toplevel, files);
359 cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
361 if (WantsComponentInstallation()) {
362 // CASE 1 : COMPONENT ALL-IN-ONE package
363 // If ALL COMPONENTS in ONE package has been requested
364 // then the package file is unique and should be open here.
365 if (componentPackageMethod == ONE_PACKAGE) {
366 return PackageComponentsAllInOne();
368 // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
369 // There will be 1 package for each component group
370 // however one may require to ignore component group and
371 // in this case you'll get 1 package for each component.
372 return PackageComponents(componentPackageMethod ==
373 ONE_PACKAGE_PER_COMPONENT);
376 // There should be one name in the packageFileNames (already, see comment
377 // in cmCPackGenerator::DoPackage(), which holds what CPack guesses
378 // will be the package filename. libpkg does something else, though,
379 // so update the single filename to what we know will be right.
380 if (this->packageFileNames.size() == 1) {
381 std::string currentPackage = this->packageFileNames[0];
382 auto lastSlash = currentPackage.rfind('/');
384 // If there is a pathname, preserve that; libpkg will write out
385 // a file with the package name and version as specified in the
386 // manifest, so we look those up (again). lastSlash is the slash
387 // itself, we need that as path separator to the calculated package name.
388 std::string actualPackage =
389 ((lastSlash != std::string::npos)
390 ? std::string(currentPackage, 0, lastSlash + 1)
392 var_lookup("CPACK_FREEBSD_PACKAGE_NAME") + '-' +
393 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION") + FreeBSDPackageSuffix_17;
395 this->packageFileNames.clear();
396 this->packageFileNames.emplace_back(actualPackage);
399 if (!pkg_initialized() && pkg_init(NULL, NULL) != EPKG_OK) {
400 cmCPackLogger(cmCPackLog::LOG_ERROR,
401 "Can not initialize FreeBSD libpkg." << std::endl);
405 std::string output_dir = cmSystemTools::CollapseFullPath("../", toplevel);
406 PkgCreate package(output_dir, toplevel, manifestname);
407 if (package.isValid()) {
408 if (!package.Create()) {
409 cmCPackLogger(cmCPackLog::LOG_ERROR,
410 "Error during pkg_create()" << std::endl);
414 cmCPackLogger(cmCPackLog::LOG_ERROR,
415 "Error before pkg_create()" << std::endl);
419 // Specifically looking for packages suffixed with the TAG, either extension
420 std::string broken_suffix_10 =
421 cmStrCat('-', var_lookup("CPACK_TOPLEVEL_TAG"), FreeBSDPackageSuffix_10);
422 std::string broken_suffix_17 =
423 cmStrCat('-', var_lookup("CPACK_TOPLEVEL_TAG"), FreeBSDPackageSuffix_17);
424 for (std::string& name : packageFileNames) {
425 cmCPackLogger(cmCPackLog::LOG_DEBUG, "Packagefile " << name << std::endl);
426 if (cmHasSuffix(name, broken_suffix_10)) {
427 name.replace(name.size() - broken_suffix_10.size(), std::string::npos,
428 FreeBSDPackageSuffix_10);
431 if (cmHasSuffix(name, broken_suffix_17)) {
432 name.replace(name.size() - broken_suffix_17.size(), std::string::npos,
433 FreeBSDPackageSuffix_17);
437 // If the name uses a *new* style name, which doesn't exist, but there
438 // is an *old* style name, then use that instead. This indicates we used
439 // an older libpkg, which still creates .txz instead of .pkg files.
440 for (std::string& name : packageFileNames) {
441 if (cmHasSuffix(name, FreeBSDPackageSuffix_17) &&
442 !cmSystemTools::FileExists(name)) {
443 const std::string badSuffix(FreeBSDPackageSuffix_17);
444 const std::string goodSuffix(FreeBSDPackageSuffix_10);
445 std::string repairedName(name);
446 repairedName.replace(repairedName.size() - badSuffix.size(),
447 std::string::npos, goodSuffix);
448 if (cmSystemTools::FileExists(repairedName)) {
450 cmCPackLogger(cmCPackLog::LOG_DEBUG,
451 "Repaired packagefile " << name << std::endl);