cppgir.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. #include "common.hpp"
  2. #include "fs.hpp"
  3. #include "genbase.hpp"
  4. #include "genns.hpp"
  5. #include "repository.hpp"
  6. #include <boost/algorithm/string/classification.hpp>
  7. #include <boost/algorithm/string/split.hpp>
  8. #include <map>
  9. #include <set>
  10. #include <vector>
  11. // thanks go to glib
  12. #define GI_STRINGIFY(macro_or_string) GI_STRINGIFY_ARG(macro_or_string)
  13. #define GI_STRINGIFY_ARG(contents) #contents
  14. #ifndef DEFAULT_IGNORE_FILE
  15. // embed ignore data
  16. #include "ignore.hpp"
  17. static std::string GI_DEFAULT_IGNORE;
  18. #else
  19. static const char *GI_DATA_IGNORE = "";
  20. static std::string GI_DEFAULT_IGNORE{GI_STRINGIFY(DEFAULT_IGNORE_FILE)};
  21. #endif
  22. Log _loglevel = Log::WARNING;
  23. class Generator
  24. {
  25. GeneratorContext &ctx_;
  26. std::vector<std::string> girdirs_;
  27. // processes ns (ns: ns header)
  28. std::map<std::string, std::string> processed_;
  29. public:
  30. Generator(GeneratorContext &_ctx, const std::vector<std::string> &girdirs)
  31. : ctx_(_ctx), girdirs_(girdirs)
  32. {}
  33. std::string find_in_dir(const fs::path p, const std::string &ns) const
  34. {
  35. std::string result;
  36. fs::error_code ec;
  37. // check if in this directory
  38. if (!fs::is_directory(p, ec))
  39. return "";
  40. auto f = p / (ns + GIR_SUFFIX);
  41. std::vector<fs::directory_entry> dirs;
  42. for (auto &&entry : fs::directory_iterator(p, ec)) {
  43. if (fs::is_directory(entry, ec)) {
  44. dirs.emplace_back(entry);
  45. } else if (f == entry) {
  46. // exact match
  47. result = f.native();
  48. } else {
  49. // non-version match
  50. auto ename = entry.path().filename().native();
  51. auto s = ns.size();
  52. if (ename.substr(0, s) == ns && ename.size() > s && ename[s] == '-')
  53. result = entry.path().native();
  54. }
  55. }
  56. // check dirs
  57. while (!dirs.empty() && result.empty()) {
  58. result = find_in_dir(dirs.back(), ns);
  59. dirs.pop_back();
  60. }
  61. return result;
  62. }
  63. std::string find(const std::string &ns) const
  64. {
  65. for (auto &&d : girdirs_) {
  66. auto res = find_in_dir(fs::path(d), ns);
  67. if (!res.empty())
  68. return res;
  69. }
  70. return "";
  71. }
  72. // gir might be:
  73. // + a GIR filename
  74. // + or a GIR namespace (ns-version)
  75. // + or a GIR namespace (no version appended)
  76. void generate(const std::string &gir, bool recurse)
  77. {
  78. fs::path f(gir);
  79. fs::error_code ec;
  80. auto path = fs::exists(f, ec) ? gir : find(gir);
  81. if (path.empty())
  82. throw std::runtime_error("could not find GIR for " + gir);
  83. // avoid reading if possible
  84. if (processed_.count(gir))
  85. return;
  86. auto genp = NamespaceGenerator::new_(ctx_, path);
  87. auto &gen = *genp;
  88. // normalize namespace
  89. auto &&ns = gen.get_ns();
  90. // prevent duplicate processing
  91. if (processed_.count(ns))
  92. return;
  93. // generate deps
  94. auto &&deps = gen.get_dependencies();
  95. std::vector<std::string> headers;
  96. if (recurse) {
  97. for (auto &&d : deps) {
  98. generate(d, recurse);
  99. // should be available now
  100. headers.emplace_back(processed_[d]);
  101. }
  102. }
  103. // now generate this one
  104. // also mark processed and retain ns header file
  105. processed_[ns] = gen.process_tree(headers);
  106. }
  107. };
  108. static std::string
  109. wrap(const char *s)
  110. {
  111. std::string res;
  112. if (s)
  113. res = s;
  114. return res;
  115. }
  116. static int
  117. die(const std::string &desc, const std::string &msg = "")
  118. {
  119. std::cout << msg << std::endl << std::endl;
  120. std::cout << desc << std::endl;
  121. return 1;
  122. }
  123. static void
  124. addsplit(std::vector<std::string> &target, const std::string &src,
  125. const std::string &seps = ":")
  126. {
  127. std::vector<std::string> tmp;
  128. boost::split(tmp, src, boost::is_any_of(seps));
  129. for (auto &&d : tmp) {
  130. if (d.size())
  131. target.emplace_back(d);
  132. }
  133. }
  134. namespace options
  135. {
  136. using OptionParseFunc = std::function<bool(const char *)>;
  137. struct Option
  138. {
  139. std::string arg;
  140. OptionParseFunc func;
  141. };
  142. Option
  143. make_parser(bool *val)
  144. {
  145. auto h = [val](const char *v) {
  146. // no arg for command-line option, set to true in that case
  147. *val = v ? atoi(v) : true;
  148. return true;
  149. };
  150. return {"", h};
  151. }
  152. Option
  153. make_parser(int *val)
  154. {
  155. auto h = [val](const char *nextarg) {
  156. try {
  157. *val = std::stoi(nextarg);
  158. return true;
  159. } catch (const std::exception &exc) {
  160. return false;
  161. }
  162. };
  163. return {"number", h};
  164. }
  165. Option
  166. make_parser(std::string *val)
  167. {
  168. auto h = [val](const char *nextarg) {
  169. *val = nextarg;
  170. return true;
  171. };
  172. return {"arg", h};
  173. }
  174. } // namespace options
  175. int
  176. main(int argc, char *argv[])
  177. {
  178. (void)argc;
  179. (void)argv;
  180. using namespace options;
  181. // env/options targets
  182. int debug_level{};
  183. std::string fpath_ignore;
  184. std::string fpath_suppress{};
  185. std::string fpath_gen_suppress;
  186. std::string output_dir;
  187. bool doclass{};
  188. bool dofullclass{};
  189. bool use_dl{};
  190. bool use_expected{};
  191. bool const_method{};
  192. bool dump_ignore{};
  193. std::string gir_path;
  194. std::string helpdesc;
  195. struct OptionData
  196. {
  197. std::string opt;
  198. std::string var;
  199. const char *desc;
  200. Option option{};
  201. };
  202. std::vector<OptionData> descs = {
  203. {"help", "", "produce help message"},
  204. {"debug", "GI_DEBUG", "debug level", make_parser(&debug_level)},
  205. {"ignore", "GI_IGNORE", "colon separated ignore files",
  206. make_parser(&fpath_ignore)},
  207. {"suppression", "GI_SUPPRESSION", "colon separated suppression files",
  208. make_parser(&fpath_suppress)},
  209. {"gen-suppression", "G_GEN_SUPPRESSION", "generate suppression file",
  210. make_parser(&fpath_gen_suppress)},
  211. {"output", "GI_OUTPUT", "output directory", make_parser(&output_dir)},
  212. {"gir-path", "GI_GIR_PATH", "colon separated GIR search path",
  213. make_parser(&gir_path)},
  214. {"class", "GI_CLASS", "generate class implementation",
  215. make_parser(&doclass)},
  216. {"class-full", "GI_CLASS_FULL", "generate fallback class methods",
  217. make_parser(&dofullclass)},
  218. {"dl", "GI_DL", "use dynamic dlopen/dlsym rather than static link",
  219. make_parser(&use_dl)},
  220. {"expected", "GI_EXPECTED", "use expected<> return rather than exception",
  221. make_parser(&use_expected)},
  222. {"const-method", "GI_CONST_METHOD", "generate const methods",
  223. make_parser(&const_method)},
  224. };
  225. // optionally dump embedded ignore
  226. if (*GI_DATA_IGNORE) {
  227. descs.push_back({"dump-ignore", "", "dump embedded ignore data",
  228. make_parser(&dump_ignore)});
  229. }
  230. std::map<std::string, OptionData *> descs_index;
  231. // first collect settings from environment
  232. for (auto &e : descs) {
  233. // build index
  234. descs_index[e.opt] = &e;
  235. // check env var
  236. if (!e.var.empty() && e.option.func) {
  237. if (auto v = getenv(e.var.c_str())) {
  238. e.option.func(v);
  239. }
  240. }
  241. }
  242. // non-option argument analogue
  243. auto gir_top = wrap(getenv("GI_GIR"));
  244. // into settings
  245. std::vector<std::string> girs, ignore_files, suppress_files;
  246. std::vector<std::string> girdirs;
  247. // collect ignore files
  248. auto fpath_ignore_env = std::move(fpath_ignore);
  249. // suppress files
  250. auto fpath_suppress_env = std::move(fpath_suppress);
  251. // GIR path
  252. auto gir_path_env = std::move(gir_path);
  253. { // basic command line processing
  254. // assemble help description
  255. auto tmpl = (R"|(
  256. {} [options] girs...
  257. Supported options and environment variables
  258. (specify 0 or 1 as variable value for a boolean switch):
  259. )|");
  260. helpdesc = fmt::format(tmpl, argv[0]);
  261. for (auto &entry : std::as_const(descs)) {
  262. std::string var = entry.var;
  263. var = !var.empty() ? fmt::format("[{}] ", var) : var;
  264. helpdesc += fmt::format(" --{:<25}{}{}\n",
  265. entry.opt + ' ' + entry.option.arg, var, entry.desc);
  266. }
  267. if (!GI_DEFAULT_IGNORE.empty()) {
  268. helpdesc +=
  269. fmt::format("\nDefault ignore files:\n{}\n", GI_DEFAULT_IGNORE);
  270. }
  271. // simple command line processing
  272. for (int i = 1; i < argc; ++i) {
  273. std::string opt = argv[i];
  274. if (opt == "-h" || opt == "--help")
  275. return die(helpdesc);
  276. if (opt.size() > 2 && opt.find("--") == 0) {
  277. auto oi = descs_index.find(opt.substr(2));
  278. if (oi != descs_index.end()) {
  279. assert(oi->second);
  280. auto &option = *oi->second;
  281. assert(option.option.func);
  282. char *nextarg = nullptr;
  283. if (!option.option.arg.empty()) {
  284. if (i + 1 >= argc) {
  285. return die(helpdesc, opt + "; missing argument");
  286. } else {
  287. nextarg = argv[i + 1];
  288. ++i;
  289. }
  290. }
  291. logger(Log::LOG,
  292. fmt::format("processing option {} {}", opt, wrap(nextarg)));
  293. if (!option.option.func(nextarg))
  294. return die(helpdesc, opt + "; invalid argument " + nextarg);
  295. }
  296. } else if (!opt.empty() && opt[0] == '-') {
  297. return die(helpdesc, "unknown option " + opt);
  298. } else {
  299. girs.push_back(opt);
  300. }
  301. }
  302. }
  303. if (dump_ignore) {
  304. std::cout << GI_DATA_IGNORE << std::endl;
  305. return 0;
  306. }
  307. // collect some more files
  308. addsplit(ignore_files, fpath_ignore);
  309. addsplit(suppress_files, fpath_suppress);
  310. addsplit(girdirs, gir_path);
  311. // add env specified
  312. addsplit(ignore_files, fpath_ignore_env);
  313. addsplit(suppress_files, fpath_suppress_env);
  314. addsplit(girdirs, gir_path_env);
  315. // level
  316. if (debug_level > 0)
  317. _loglevel = (Log)debug_level;
  318. // system default
  319. {
  320. const char PATH_SEP = '/';
  321. // gobject-introspection considers XDG_DATA_HOME first
  322. auto xdg_data_home = wrap(getenv("XDG_DATA_HOME"));
  323. if (xdg_data_home.empty()) {
  324. xdg_data_home = wrap(getenv("HOME"));
  325. if (!xdg_data_home.empty()) {
  326. if (xdg_data_home.back() != PATH_SEP)
  327. xdg_data_home += PATH_SEP;
  328. xdg_data_home += fmt::format(".local{}share", PATH_SEP);
  329. }
  330. }
  331. std::vector<std::string> default_gir_dirs;
  332. if (!xdg_data_home.empty())
  333. default_gir_dirs.push_back(xdg_data_home);
  334. // gobject-introspection uses XDG_DATA_DIRS next
  335. auto xdg_data_dirs = wrap(getenv("XDG_DATA_DIRS"));
  336. addsplit(default_gir_dirs, xdg_data_dirs);
  337. #ifdef DEFAULT_GIRPATH
  338. // optional fallback
  339. if (default_gir_dirs.empty())
  340. addsplit(default_gir_dirs, GI_STRINGIFY(DEFAULT_GIRPATH));
  341. #endif
  342. for (auto &d : default_gir_dirs) {
  343. if (d.back() != PATH_SEP)
  344. d += PATH_SEP;
  345. d += "gir-1.0";
  346. girdirs.push_back(std::move(d));
  347. }
  348. }
  349. // extra GIR paths that gobject-introspection considers
  350. #ifdef GI_GIR_DIR
  351. girdirs.push_back(GI_STRINGIFY(GI_GIR_DIR));
  352. #endif
  353. #ifdef GI_DATA_DIR
  354. girdirs.push_back(GI_STRINGIFY(GI_DATA_DIR));
  355. #endif
  356. #ifdef GI_DEF_DIR
  357. girdirs.push_back(GI_STRINGIFY(GI_DEF_DIR));
  358. #endif
  359. for (auto &&d : girdirs)
  360. logger(Log::DEBUG, "extending GIR path " + d);
  361. // system default
  362. if (!GI_DEFAULT_IGNORE.empty())
  363. addsplit(ignore_files, GI_DEFAULT_IGNORE);
  364. // collect girs to process
  365. addsplit(girs, gir_top);
  366. // sanity check
  367. if (output_dir.empty())
  368. return die(helpdesc, "missing output directory");
  369. if (girs.empty())
  370. return die(helpdesc, "nothing to process");
  371. // check for now
  372. if (girdirs.empty())
  373. return die(helpdesc, "empty search path");
  374. // at least the standard ignore file is required
  375. // or things will go wrong
  376. int cnt = 0;
  377. fs::error_code ec;
  378. for (auto &f : ignore_files)
  379. cnt += fs::exists(f, ec);
  380. if (cnt == 0 && !GI_DEFAULT_IGNORE.empty())
  381. return die(helpdesc, "required default ignore file location not specified");
  382. auto match_ignore = Matcher(ignore_files, GI_DATA_IGNORE);
  383. auto match_suppress = Matcher(suppress_files);
  384. // now let's start
  385. GeneratorOptions options;
  386. options.rootdir = output_dir;
  387. options.classimpl = doclass;
  388. options.classfull = dofullclass;
  389. options.dl = use_dl;
  390. options.expected = use_expected;
  391. options.const_method = const_method;
  392. logger(Log::INFO, "generating to directory {}", options.rootdir);
  393. auto repo = Repository::new_();
  394. std::set<std::string> suppressions;
  395. GeneratorContext ctx{
  396. options, *repo, match_ignore, match_suppress, suppressions};
  397. Generator gen(ctx, girdirs);
  398. try {
  399. for (auto &&g : girs)
  400. gen.generate(g, true);
  401. // write suppression
  402. if (fpath_gen_suppress.size()) {
  403. std::vector<std::string> sup(suppressions.begin(), suppressions.end());
  404. sort(sup.begin(), sup.end());
  405. logger(Log::INFO, "writing {} suppressions to {}", sup.size(),
  406. fpath_gen_suppress);
  407. std::ofstream fsup(fpath_gen_suppress);
  408. for (auto &&v : sup)
  409. fsup << v << std::endl;
  410. }
  411. } catch (std::runtime_error &ex) {
  412. logger(Log::ERROR, ex.what());
  413. return EXIT_FAILURE;
  414. }
  415. return EXIT_SUCCESS;
  416. }