cmCPackFreeBSDGenerator.cxx 13 KB

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