cmCPackFreeBSDGenerator.cxx 14 KB

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