cmCPackFreeBSDGenerator.cxx 14 KB

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