cmCPackFreeBSDGenerator.cxx 13 KB

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