| /* 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 "cmArchiveWrite.h" |
| #include "cmCPackArchiveGenerator.h" |
| #include "cmCPackLog.h" |
| #include "cmGeneratedFileStream.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmWorkingDirectory.h" |
| |
| // Needed for ::open() and ::stat() |
| #include <algorithm> |
| #include <ostream> |
| #include <utility> |
| #include <vector> |
| |
| #include <fcntl.h> |
| #include <pkg.h> |
| |
| #include <sys/stat.h> |
| |
| // Suffix used to tell libpkg what compression to use |
| static const char FreeBSDPackageCompression[] = "txz"; |
| static const char 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(const std::string& output_dir, const std::string& toplevel_dir, |
| const std::string& 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: |
| const std::string& value; |
| |
| EscapeQuotes(const std::string& s) |
| : value(s) |
| { |
| } |
| }; |
| |
| // Output a string as "string" with escaping applied. |
| cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s, |
| const EscapeQuotes& 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(const std::string& 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(const std::string& k) |
| : ManifestKey(k) |
| { |
| } |
| |
| ManifestKeyListValue& operator<<(const std::string& v) |
| { |
| value.push_back(v); |
| return *this; |
| } |
| |
| ManifestKeyListValue& operator<<(const std::vector<std::string>& 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(const std::string& 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, |
| const ManifestKey& 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 NULL-ptr return from GetOption(). |
| std::string cmCPackFreeBSDGenerator::var_lookup(const char* 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")); |
| std::vector<std::string> licenses = |
| cmExpandedList(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); |
| std::vector<std::string> categories = |
| cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES")); |
| manifest << (ManifestKeyListValue("categories") << categories); |
| manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX")); |
| std::vector<std::string> deps = |
| cmExpandedList(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(const std::string& 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, |
| const std::string& toplevel, |
| const std::vector<std::string>& 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(NULL, NULL) != EPKG_OK) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Can not initialize FreeBSD libpkg." << std::endl); |
| return 0; |
| } |
| |
| const std::string output_dir = |
| cmSystemTools::CollapseFullPath("../", 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; |
| } |
| } |
| |
| const std::string 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. |
| const std::string sourceFile = packageFileNames[0]; |
| const std::string packageSubDirectory = |
| cmSystemTools::GetParentDirectory(sourceFile); |
| const std::string targetFileName = |
| packageSubDirectory + '/' + packageFileName; |
| if (cmSystemTools::RenameFile(sourceFile, targetFileName)) { |
| this->packageFileNames.clear(); |
| this->packageFileNames.emplace_back(targetFileName); |
| } |
| } |
| |
| return 1; |
| } |