123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmCPackFreeBSDGenerator.h"
- #include <algorithm>
- #include <ostream>
- #include <utility>
- #include <vector>
- #include <fcntl.h>
- #include <pkg.h>
- #include <sys/stat.h>
- #include "cmArchiveWrite.h"
- #include "cmCPackArchiveGenerator.h"
- #include "cmCPackLog.h"
- #include "cmGeneratedFileStream.h"
- #include "cmList.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- #include "cmWorkingDirectory.h"
- // Suffix used to tell libpkg what compression to use
- static char const FreeBSDPackageCompression[] = "txz";
- static char const FreeBSDPackageSuffix_17[] = ".pkg";
- cmCPackFreeBSDGenerator::cmCPackFreeBSDGenerator()
- : cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr",
- FreeBSDPackageSuffix_17)
- {
- }
- int cmCPackFreeBSDGenerator::InitializeInternal()
- {
- this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr/local");
- this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "0");
- return this->Superclass::InitializeInternal();
- }
- cmCPackFreeBSDGenerator::~cmCPackFreeBSDGenerator() = default;
- // This is a wrapper for struct pkg_create and pkg_create()
- //
- // Instantiate this class with suitable parameters, then
- // check isValid() to check if it's ok. Afterwards, call
- // Create() to do the actual work. This will leave a package
- // in the given `output_dir`.
- //
- // This wrapper cleans up the struct pkg_create.
- class PkgCreate
- {
- public:
- PkgCreate()
- : d(nullptr)
- {
- }
- PkgCreate(std::string const& output_dir, std::string const& toplevel_dir,
- std::string const& manifest_name)
- : d(pkg_create_new())
- , manifest(manifest_name)
- {
- if (d) {
- pkg_create_set_format(d, FreeBSDPackageCompression);
- pkg_create_set_compression_level(d, 0); // Explicitly set default
- pkg_create_set_overwrite(d, false);
- pkg_create_set_rootdir(d, toplevel_dir.c_str());
- pkg_create_set_output_dir(d, output_dir.c_str());
- }
- }
- ~PkgCreate()
- {
- if (d)
- pkg_create_free(d);
- }
- bool isValid() const { return d; }
- bool Create()
- {
- if (!isValid())
- return false;
- // The API in the FreeBSD sources (the header has no documentation),
- // is as follows:
- //
- // int pkg_create(struct pkg_create *pc, const char *metadata, const char
- // *plist, bool hash)
- //
- // We let the plist be determined from what is installed, and all
- // the rest comes from the manifest data.
- int r = pkg_create(d, manifest.c_str(), nullptr, false);
- return r == 0;
- }
- private:
- struct pkg_create* d;
- std::string manifest;
- };
- // This is a wrapper, for use only in stream-based output,
- // that will output a string in UCL escaped fashion (in particular,
- // quotes and backslashes are escaped). The list of characters
- // to escape is taken from https://github.com/vstakhov/libucl
- // (which is the reference implementation pkg(8) refers to).
- class EscapeQuotes
- {
- public:
- std::string const& value;
- EscapeQuotes(std::string const& s)
- : value(s)
- {
- }
- };
- // Output a string as "string" with escaping applied.
- cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
- EscapeQuotes const& v)
- {
- s << '"';
- for (char c : v.value) {
- switch (c) {
- case '\n':
- s << "\\n";
- break;
- case '\r':
- s << "\\r";
- break;
- case '\b':
- s << "\\b";
- break;
- case '\t':
- s << "\\t";
- break;
- case '\f':
- s << "\\f";
- break;
- case '\\':
- s << "\\\\";
- break;
- case '"':
- s << "\\\"";
- break;
- default:
- s << c;
- break;
- }
- }
- s << '"';
- return s;
- }
- // The following classes are all helpers for writing out the UCL
- // manifest file (it also looks like JSON). ManifestKey just has
- // a (string-valued) key; subclasses add a specific kind of
- // value-type to the key, and implement write_value() to output
- // the corresponding UCL.
- class ManifestKey
- {
- public:
- std::string key;
- ManifestKey(std::string k)
- : key(std::move(k))
- {
- }
- virtual ~ManifestKey() = default;
- // Output the value associated with this key to the stream @p s.
- // Format is to be decided by subclasses.
- virtual void write_value(cmGeneratedFileStream& s) const = 0;
- };
- // Basic string-value (e.g. "name": "cmake")
- class ManifestKeyValue : public ManifestKey
- {
- public:
- std::string value;
- ManifestKeyValue(std::string const& k, std::string v)
- : ManifestKey(k)
- , value(std::move(v))
- {
- }
- void write_value(cmGeneratedFileStream& s) const override
- {
- s << EscapeQuotes(value);
- }
- };
- // List-of-strings values (e.g. "licenses": ["GPLv2", "LGPLv2"])
- class ManifestKeyListValue : public ManifestKey
- {
- public:
- using VList = std::vector<std::string>;
- VList value;
- ManifestKeyListValue(std::string const& k)
- : ManifestKey(k)
- {
- }
- ManifestKeyListValue& operator<<(std::string const& v)
- {
- value.push_back(v);
- return *this;
- }
- ManifestKeyListValue& operator<<(std::vector<std::string> const& v)
- {
- for (std::string const& e : v) {
- (*this) << e;
- }
- return *this;
- }
- void write_value(cmGeneratedFileStream& s) const override
- {
- bool with_comma = false;
- s << '[';
- for (std::string const& elem : value) {
- s << (with_comma ? ',' : ' ');
- s << EscapeQuotes(elem);
- with_comma = true;
- }
- s << " ]";
- }
- };
- // Deps: actually a dictionary, but we'll treat it as a
- // list so we only name the deps, and produce dictionary-
- // like output via write_value()
- class ManifestKeyDepsValue : public ManifestKeyListValue
- {
- public:
- ManifestKeyDepsValue(std::string const& k)
- : ManifestKeyListValue(k)
- {
- }
- void write_value(cmGeneratedFileStream& s) const override
- {
- s << "{\n";
- for (std::string const& elem : value) {
- s << " \"" << elem << R"(": {"origin": ")" << elem << "\"},\n";
- }
- s << '}';
- }
- };
- // Write one of the key-value classes (above) to the stream @p s
- cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
- ManifestKey const& v)
- {
- s << '"' << v.key << "\": ";
- v.write_value(s);
- s << ",\n";
- return s;
- }
- // Look up variable; if no value is set, returns an empty string;
- // basically a wrapper that handles the nullptr return from GetOption().
- std::string cmCPackFreeBSDGenerator::var_lookup(char const* var_name)
- {
- cmValue pv = this->GetOption(var_name);
- if (!pv) {
- return {};
- }
- return *pv;
- }
- // Produce UCL in the given @p manifest file for the common
- // manifest fields (common to the compact and regular formats),
- // by reading the CPACK_FREEBSD_* variables.
- void cmCPackFreeBSDGenerator::write_manifest_fields(
- cmGeneratedFileStream& manifest)
- {
- manifest << ManifestKeyValue("name",
- var_lookup("CPACK_FREEBSD_PACKAGE_NAME"));
- manifest << ManifestKeyValue("origin",
- var_lookup("CPACK_FREEBSD_PACKAGE_ORIGIN"));
- manifest << ManifestKeyValue("version",
- var_lookup("CPACK_FREEBSD_PACKAGE_VERSION"));
- manifest << ManifestKeyValue("maintainer",
- var_lookup("CPACK_FREEBSD_PACKAGE_MAINTAINER"));
- manifest << ManifestKeyValue("comment",
- var_lookup("CPACK_FREEBSD_PACKAGE_COMMENT"));
- manifest << ManifestKeyValue(
- "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
- manifest << ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
- cmList licenses{ var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE") };
- std::string licenselogic("single");
- if (licenses.empty()) {
- cmSystemTools::SetFatalErrorOccurred();
- } else if (licenses.size() > 1) {
- licenselogic = var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
- }
- manifest << ManifestKeyValue("licenselogic", licenselogic);
- manifest << (ManifestKeyListValue("licenses") << licenses);
- cmList categories{ var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES") };
- manifest << (ManifestKeyListValue("categories") << categories);
- manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
- cmList deps{ var_lookup("CPACK_FREEBSD_PACKAGE_DEPS") };
- if (!deps.empty()) {
- manifest << (ManifestKeyDepsValue("deps") << deps);
- }
- }
- // Package only actual files; others are ignored (in particular,
- // intermediate subdirectories are ignored).
- static bool ignore_file(std::string const& filename)
- {
- struct stat statbuf;
- return stat(filename.c_str(), &statbuf) < 0 ||
- (statbuf.st_mode & S_IFMT) != S_IFREG;
- }
- // Write the given list of @p files to the manifest stream @p s,
- // as the UCL field "files" (which is dictionary-valued, to
- // associate filenames with hashes). All the files are transformed
- // to paths relative to @p toplevel, with a leading / (since the paths
- // in FreeBSD package files are supposed to be absolute).
- void write_manifest_files(cmGeneratedFileStream& s,
- std::string const& toplevel,
- std::vector<std::string> const& files)
- {
- s << "\"files\": {\n";
- for (std::string const& file : files) {
- s << " \"/" << cmSystemTools::RelativePath(toplevel, file) << "\": \""
- << "<sha256>" // this gets replaced by libpkg by the actual SHA256
- << "\",\n";
- }
- s << " },\n";
- }
- int cmCPackFreeBSDGenerator::PackageFiles()
- {
- if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error while executing CPackFreeBSD.cmake" << std::endl);
- return 0;
- }
- cmWorkingDirectory wd(toplevel);
- files.erase(std::remove_if(files.begin(), files.end(), ignore_file),
- files.end());
- std::string manifestname = toplevel + "/+MANIFEST";
- {
- cmGeneratedFileStream manifest(manifestname);
- manifest << "{\n";
- write_manifest_fields(manifest);
- write_manifest_files(manifest, toplevel, files);
- manifest << "}\n";
- }
- cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
- if (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 (componentPackageMethod == ONE_PACKAGE) {
- return 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 PackageComponents(componentPackageMethod ==
- ONE_PACKAGE_PER_COMPONENT);
- }
- // There should be one name in the packageFileNames (already, see comment
- // in cmCPackGenerator::DoPackage(), which holds what CPack guesses
- // will be the package filename. libpkg does something else, though,
- // so update the single filename to what we know will be right.
- if (this->packageFileNames.size() == 1) {
- std::string currentPackage = this->packageFileNames[0];
- auto lastSlash = currentPackage.rfind('/');
- // If there is a pathname, preserve that; libpkg will write out
- // a file with the package name and version as specified in the
- // manifest, so we look those up (again). lastSlash is the slash
- // itself, we need that as path separator to the calculated package name.
- std::string actualPackage =
- ((lastSlash != std::string::npos)
- ? std::string(currentPackage, 0, lastSlash + 1)
- : std::string()) +
- var_lookup("CPACK_FREEBSD_PACKAGE_NAME") + '-' +
- var_lookup("CPACK_FREEBSD_PACKAGE_VERSION") + FreeBSDPackageSuffix_17;
- this->packageFileNames.clear();
- this->packageFileNames.emplace_back(actualPackage);
- }
- if (!pkg_initialized() && pkg_init(nullptr, nullptr) != EPKG_OK) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Can not initialize FreeBSD libpkg." << std::endl);
- return 0;
- }
- std::string const output_dir = cmSystemTools::GetFilenamePath(toplevel);
- PkgCreate package(output_dir, toplevel, manifestname);
- if (package.isValid()) {
- if (!package.Create()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error during pkg_create()" << std::endl);
- return 0;
- }
- } else {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error before pkg_create()" << std::endl);
- return 0;
- }
- // Specifically looking for packages suffixed with the TAG
- std::string broken_suffix_17 =
- cmStrCat('-', var_lookup("CPACK_TOPLEVEL_TAG"), FreeBSDPackageSuffix_17);
- for (std::string& name : packageFileNames) {
- cmCPackLogger(cmCPackLog::LOG_DEBUG, "Packagefile " << name << std::endl);
- if (cmHasSuffix(name, broken_suffix_17)) {
- name.replace(name.size() - broken_suffix_17.size(), std::string::npos,
- FreeBSDPackageSuffix_17);
- break;
- }
- }
- std::string const packageFileName =
- var_lookup("CPACK_PACKAGE_FILE_NAME") + FreeBSDPackageSuffix_17;
- if (packageFileNames.size() == 1 && !packageFileName.empty() &&
- packageFileNames[0] != packageFileName) {
- // Since libpkg always writes <name>-<version>.<suffix>,
- // if there is a CPACK_PACKAGE_FILE_NAME set, we need to
- // rename, and then re-set the name.
- std::string const sourceFile = packageFileNames[0];
- std::string const packageSubDirectory =
- cmSystemTools::GetParentDirectory(sourceFile);
- std::string const targetFileName =
- packageSubDirectory + '/' + packageFileName;
- if (cmSystemTools::RenameFile(sourceFile, targetFileName)) {
- this->packageFileNames.clear();
- this->packageFileNames.emplace_back(targetFileName);
- }
- }
- return 1;
- }
|