cmCPackFreeBSDGenerator.cxx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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 "cmSystemTools.h"
  9. // Needed for ::open() and ::stat()
  10. #include <fcntl.h>
  11. #include <sys/stat.h>
  12. #include <sys/types.h>
  13. #include <unistd.h>
  14. #include <pkg.h>
  15. #include <algorithm>
  16. #include <utility>
  17. cmCPackFreeBSDGenerator::cmCPackFreeBSDGenerator()
  18. : cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr")
  19. {
  20. }
  21. int cmCPackFreeBSDGenerator::InitializeInternal()
  22. {
  23. this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr/local");
  24. this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "0");
  25. return this->Superclass::InitializeInternal();
  26. }
  27. cmCPackFreeBSDGenerator::~cmCPackFreeBSDGenerator() = default;
  28. // This is a wrapper, for use only in stream-based output,
  29. // that will output a string in UCL escaped fashion (in particular,
  30. // quotes and backslashes are escaped). The list of characters
  31. // to escape is taken from https://github.com/vstakhov/libucl
  32. // (which is the reference implementation pkg(8) refers to).
  33. class EscapeQuotes
  34. {
  35. public:
  36. const std::string& value;
  37. EscapeQuotes(const std::string& s)
  38. : value(s)
  39. {
  40. }
  41. };
  42. // Output a string as "string" with escaping applied.
  43. cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
  44. const EscapeQuotes& v)
  45. {
  46. s << '"';
  47. for (char c : v.value) {
  48. switch (c) {
  49. case '\n':
  50. s << "\\n";
  51. break;
  52. case '\r':
  53. s << "\\r";
  54. break;
  55. case '\b':
  56. s << "\\b";
  57. break;
  58. case '\t':
  59. s << "\\t";
  60. break;
  61. case '\f':
  62. s << "\\f";
  63. break;
  64. case '\\':
  65. s << "\\\\";
  66. break;
  67. case '"':
  68. s << "\\\"";
  69. break;
  70. default:
  71. s << c;
  72. break;
  73. }
  74. }
  75. s << '"';
  76. return s;
  77. }
  78. // The following classes are all helpers for writing out the UCL
  79. // manifest file (it also looks like JSON). ManifestKey just has
  80. // a (string-valued) key; subclasses add a specific kind of
  81. // value-type to the key, and implement write_value() to output
  82. // the corresponding UCL.
  83. class ManifestKey
  84. {
  85. public:
  86. std::string key;
  87. ManifestKey(std::string k)
  88. : key(std::move(k))
  89. {
  90. }
  91. virtual ~ManifestKey() = default;
  92. // Output the value associated with this key to the stream @p s.
  93. // Format is to be decided by subclasses.
  94. virtual void write_value(cmGeneratedFileStream& s) const = 0;
  95. };
  96. // Basic string-value (e.g. "name": "cmake")
  97. class ManifestKeyValue : public ManifestKey
  98. {
  99. public:
  100. std::string value;
  101. ManifestKeyValue(const std::string& k, std::string v)
  102. : ManifestKey(k)
  103. , value(std::move(v))
  104. {
  105. }
  106. void write_value(cmGeneratedFileStream& s) const override
  107. {
  108. s << EscapeQuotes(value);
  109. }
  110. };
  111. // List-of-strings values (e.g. "licenses": ["GPLv2", "LGPLv2"])
  112. class ManifestKeyListValue : public ManifestKey
  113. {
  114. public:
  115. typedef std::vector<std::string> VList;
  116. VList value;
  117. ManifestKeyListValue(const std::string& k)
  118. : ManifestKey(k)
  119. {
  120. }
  121. ManifestKeyListValue& operator<<(const std::string& v)
  122. {
  123. value.push_back(v);
  124. return *this;
  125. }
  126. ManifestKeyListValue& operator<<(const std::vector<std::string>& v)
  127. {
  128. for (std::string const& e : v) {
  129. (*this) << e;
  130. }
  131. return *this;
  132. }
  133. void write_value(cmGeneratedFileStream& s) const override
  134. {
  135. bool with_comma = false;
  136. s << '[';
  137. for (std::string const& elem : value) {
  138. s << (with_comma ? ',' : ' ');
  139. s << EscapeQuotes(elem);
  140. with_comma = true;
  141. }
  142. s << " ]";
  143. }
  144. };
  145. // Deps: actually a dictionary, but we'll treat it as a
  146. // list so we only name the deps, and produce dictionary-
  147. // like output via write_value()
  148. class ManifestKeyDepsValue : public ManifestKeyListValue
  149. {
  150. public:
  151. ManifestKeyDepsValue(const std::string& k)
  152. : ManifestKeyListValue(k)
  153. {
  154. }
  155. void write_value(cmGeneratedFileStream& s) const override
  156. {
  157. s << "{\n";
  158. for (std::string const& elem : value) {
  159. s << " \"" << elem << "\": {\"origin\": \"" << elem << "\"},\n";
  160. }
  161. s << '}';
  162. }
  163. };
  164. // Write one of the key-value classes (above) to the stream @p s
  165. cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
  166. const ManifestKey& v)
  167. {
  168. s << '"' << v.key << "\": ";
  169. v.write_value(s);
  170. s << ",\n";
  171. return s;
  172. }
  173. // Look up variable; if no value is set, returns an empty string;
  174. // basically a wrapper that handles the NULL-ptr return from GetOption().
  175. std::string cmCPackFreeBSDGenerator::var_lookup(const char* var_name)
  176. {
  177. const char* pv = this->GetOption(var_name);
  178. if (!pv) {
  179. return std::string();
  180. }
  181. return pv;
  182. }
  183. // Produce UCL in the given @p manifest file for the common
  184. // manifest fields (common to the compact and regular formats),
  185. // by reading the CPACK_FREEBSD_* variables.
  186. void cmCPackFreeBSDGenerator::write_manifest_fields(
  187. cmGeneratedFileStream& manifest)
  188. {
  189. manifest << ManifestKeyValue("name",
  190. var_lookup("CPACK_FREEBSD_PACKAGE_NAME"));
  191. manifest << ManifestKeyValue("origin",
  192. var_lookup("CPACK_FREEBSD_PACKAGE_ORIGIN"));
  193. manifest << ManifestKeyValue("version",
  194. var_lookup("CPACK_FREEBSD_PACKAGE_VERSION"));
  195. manifest << ManifestKeyValue("maintainer",
  196. var_lookup("CPACK_FREEBSD_PACKAGE_MAINTAINER"));
  197. manifest << ManifestKeyValue("comment",
  198. var_lookup("CPACK_FREEBSD_PACKAGE_COMMENT"));
  199. manifest << ManifestKeyValue(
  200. "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
  201. manifest << ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
  202. std::vector<std::string> licenses;
  203. cmSystemTools::ExpandListArgument(
  204. var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE"), licenses);
  205. std::string licenselogic("single");
  206. if (licenses.empty()) {
  207. cmSystemTools::SetFatalErrorOccured();
  208. } else if (licenses.size() > 1) {
  209. licenselogic = var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
  210. }
  211. manifest << ManifestKeyValue("licenselogic", licenselogic);
  212. manifest << (ManifestKeyListValue("licenses") << licenses);
  213. std::vector<std::string> categories;
  214. cmSystemTools::ExpandListArgument(
  215. var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES"), categories);
  216. manifest << (ManifestKeyListValue("categories") << categories);
  217. manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
  218. std::vector<std::string> deps;
  219. cmSystemTools::ExpandListArgument(var_lookup("CPACK_FREEBSD_PACKAGE_DEPS"),
  220. deps);
  221. if (!deps.empty()) {
  222. manifest << (ManifestKeyDepsValue("deps") << deps);
  223. }
  224. }
  225. // Package only actual files; others are ignored (in particular,
  226. // intermediate subdirectories are ignored).
  227. static bool ignore_file(const std::string& filename)
  228. {
  229. struct stat statbuf;
  230. return stat(filename.c_str(), &statbuf) < 0 ||
  231. (statbuf.st_mode & S_IFMT) != S_IFREG;
  232. }
  233. // Write the given list of @p files to the manifest stream @p s,
  234. // as the UCL field "files" (which is dictionary-valued, to
  235. // associate filenames with hashes). All the files are transformed
  236. // to paths relative to @p toplevel, with a leading / (since the paths
  237. // in FreeBSD package files are supposed to be absolute).
  238. void write_manifest_files(cmGeneratedFileStream& s,
  239. const std::string& toplevel,
  240. const std::vector<std::string>& files)
  241. {
  242. s << "\"files\": {\n";
  243. for (std::string const& file : files) {
  244. s << " \"/" << cmSystemTools::RelativePath(toplevel, file) << "\": \""
  245. << "<sha256>"
  246. << "\",\n";
  247. }
  248. s << " },\n";
  249. }
  250. static bool has_suffix(const std::string& str, const std::string& suffix)
  251. {
  252. return str.size() >= suffix.size() &&
  253. str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
  254. }
  255. int cmCPackFreeBSDGenerator::PackageFiles()
  256. {
  257. if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
  258. cmCPackLogger(cmCPackLog::LOG_ERROR,
  259. "Error while execution CPackFreeBSD.cmake" << std::endl);
  260. return 0;
  261. }
  262. std::vector<std::string>::const_iterator fileIt;
  263. std::string dir = cmSystemTools::GetCurrentWorkingDirectory();
  264. cmSystemTools::ChangeDirectory(toplevel);
  265. files.erase(std::remove_if(files.begin(), files.end(), ignore_file),
  266. files.end());
  267. std::string manifestname = toplevel + "/+MANIFEST";
  268. {
  269. cmGeneratedFileStream manifest(manifestname);
  270. manifest << "{\n";
  271. write_manifest_fields(manifest);
  272. write_manifest_files(manifest, toplevel, files);
  273. manifest << "}\n";
  274. }
  275. cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
  276. if (WantsComponentInstallation()) {
  277. // CASE 1 : COMPONENT ALL-IN-ONE package
  278. // If ALL COMPONENTS in ONE package has been requested
  279. // then the package file is unique and should be open here.
  280. if (componentPackageMethod == ONE_PACKAGE) {
  281. return PackageComponentsAllInOne();
  282. }
  283. // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
  284. // There will be 1 package for each component group
  285. // however one may require to ignore component group and
  286. // in this case you'll get 1 package for each component.
  287. return PackageComponents(componentPackageMethod ==
  288. ONE_PACKAGE_PER_COMPONENT);
  289. }
  290. std::string output_dir =
  291. cmSystemTools::CollapseCombinedPath(toplevel, "../");
  292. pkg_create_from_manifest(output_dir.c_str(), ::TXZ, toplevel.c_str(),
  293. manifestname.c_str(), nullptr);
  294. std::string broken_suffix = std::string("-") +
  295. var_lookup("CPACK_TOPLEVEL_TAG") + std::string(GetOutputExtension());
  296. for (std::string& name : packageFileNames) {
  297. cmCPackLogger(cmCPackLog::LOG_DEBUG, "Packagefile " << name << std::endl);
  298. if (has_suffix(name, broken_suffix)) {
  299. name.replace(name.size() - broken_suffix.size(), std::string::npos,
  300. GetOutputExtension());
  301. break;
  302. }
  303. }
  304. cmSystemTools::ChangeDirectory(dir);
  305. return 1;
  306. }