Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
1742 lines
59 KiB
C++
1742 lines
59 KiB
C++
#include "genns.hpp"
|
||
#include "fs.hpp"
|
||
#include "function.hpp"
|
||
#include "genbase.hpp"
|
||
|
||
#include <boost/algorithm/string/classification.hpp>
|
||
#include <boost/algorithm/string/erase.hpp>
|
||
#include <boost/algorithm/string/join.hpp>
|
||
#include <boost/algorithm/string/replace.hpp>
|
||
#include <boost/algorithm/string/split.hpp>
|
||
|
||
#include <boost/property_tree/xml_parser.hpp>
|
||
|
||
#include <iostream>
|
||
#include <ostream>
|
||
#include <sstream>
|
||
#include <tuple>
|
||
|
||
namespace
|
||
{
|
||
class File : public std::ofstream
|
||
{
|
||
std::string ns;
|
||
std::string fname;
|
||
bool preamble;
|
||
bool guard;
|
||
bool nsdir;
|
||
NamespaceGuard nsg;
|
||
|
||
static std::string root;
|
||
|
||
public:
|
||
// ok, single threaded processing
|
||
static void set_root(const std::string &dir) { root = dir; }
|
||
|
||
static std::string prepdirs(
|
||
const std::string &_ns, const std::string &_fname, bool nsdir)
|
||
{
|
||
fs::path p(root);
|
||
if (nsdir)
|
||
p /= tolower(_ns);
|
||
fs::create_directories(p);
|
||
p /= tolower(_fname);
|
||
return p.native();
|
||
}
|
||
|
||
File(const std::string &_ns, const std::string _fname, bool _need_ns = true,
|
||
bool _need_guard = true, bool _nsdir = true)
|
||
: std::ofstream(prepdirs(_ns, _fname, _nsdir)), ns(_ns), fname(_fname),
|
||
preamble(_need_ns), guard(_need_guard), nsdir(_nsdir), nsg(*this)
|
||
{
|
||
write_pre();
|
||
}
|
||
|
||
std::string get_rel_path() const
|
||
{
|
||
return nsdir ? (fs::path(tolower(ns)) / tolower(fname)).native()
|
||
: tolower(fname);
|
||
}
|
||
|
||
void write_pre()
|
||
{
|
||
*this << "// AUTO-GENERATED\n\n";
|
||
auto definc = fmt::format("_GI_{}_{}_", toupper(ns), toupper(fname));
|
||
for (auto &v : definc)
|
||
if (!isalnum(v))
|
||
v = '_';
|
||
if (guard) {
|
||
*this << "#ifndef " << definc << std::endl;
|
||
*this << "#define " << definc << std::endl;
|
||
*this << std::endl;
|
||
}
|
||
if (preamble)
|
||
nsg.push(ns);
|
||
}
|
||
|
||
void write_post()
|
||
{
|
||
if (preamble)
|
||
nsg.pop();
|
||
if (guard)
|
||
*this << "#endif" << std::endl;
|
||
}
|
||
|
||
~File() { write_post(); }
|
||
};
|
||
|
||
std::string File::root;
|
||
|
||
class NamespaceGeneratorImpl : private GeneratorBase, public NamespaceGenerator
|
||
{
|
||
std::string version_;
|
||
std::vector<std::string> deps_;
|
||
pt::ptree root_;
|
||
pt::ptree tree_;
|
||
// helper exp
|
||
std::regex re_unqualify_;
|
||
bool allow_deprecated_{};
|
||
|
||
public:
|
||
NamespaceGeneratorImpl(GeneratorContext &ctx, const std::string &filename)
|
||
: GeneratorBase(ctx, "")
|
||
{
|
||
logger(Log::INFO, "reading {}", filename);
|
||
pt::read_xml(filename, root_);
|
||
// extract dependencies
|
||
for (auto &&n : root_.get_child("repository")) {
|
||
if (n.first == "include") {
|
||
auto &&name = get_name(n.second);
|
||
auto &&version = get_attribute(n.second, AT_VERSION);
|
||
deps_.emplace_back(name + "-" + version);
|
||
logger(Log::INFO, indent + "dependency " + deps_.back());
|
||
}
|
||
}
|
||
// all else is in a subtree
|
||
tree_ = root_.get_child("repository.namespace");
|
||
ns = get_name(tree_);
|
||
version_ = get_attribute(tree_, AT_VERSION);
|
||
re_unqualify_ =
|
||
std::regex(fmt::format("^{}(::|\\.)", ns), std::regex::optimize);
|
||
}
|
||
|
||
std::string get_ns() const { return ns + "-" + version_; }
|
||
|
||
std::vector<std::string> get_dependencies() const { return deps_; }
|
||
|
||
private:
|
||
// in most cases no problems arise if both M1.SomeName and M2.SomeOtherName
|
||
// both map to GSomeName
|
||
// but some generated code, like declare_XXX template specialization assumes
|
||
// a 1-to-1 mapping, as should be the case in with sane GIR
|
||
// however, if the latter are somehow not sane, then duplicate definitions
|
||
// might arise (ODR and all that), although it *really is* a GIR bug
|
||
//
|
||
// so try to mitigate that here
|
||
// check that only 1 (qualified) girname/cppname claims/maps to a ctype
|
||
void check_odr(const std::string &cpptype, const std::string &ctype) const
|
||
{
|
||
// verify this ctype has not been seen before
|
||
// a particular ctype can only be defined/claimed by one GIR symbol
|
||
// (in one module), otherwise lots of things will go wrong
|
||
// GIRs also have an "alias" concept, so that should be used instead
|
||
auto conflict = ctx.repo.check_odr(cpptype, ctype);
|
||
if (!conflict.empty()) {
|
||
throw std::runtime_error(
|
||
fmt::format("{} maps to {}, already claimed by {};\n"
|
||
"Please verify/fix GIRs and/or add ignore as needed.",
|
||
cpptype, ctype, conflict));
|
||
}
|
||
}
|
||
|
||
std::string make_declare(bool decl_ctype, const std::string &cpptype,
|
||
const std::string &ctype) const
|
||
{
|
||
check_odr(cpptype, ctype);
|
||
return fmt::format(
|
||
"template<> struct declare_{}_of<{}>\n{{ typedef {} type; }}; ",
|
||
(decl_ctype ? "ctype" : "cpptype"), (decl_ctype ? cpptype : ctype),
|
||
(decl_ctype ? ctype : cpptype));
|
||
}
|
||
|
||
void process_element_alias(const pt::ptree &node, std::ostream &out) const
|
||
{
|
||
std::ostringstream oss;
|
||
auto name = get_name(node);
|
||
// skip some stuff
|
||
if (name.find("autoptr") != name.npos)
|
||
throw skip("autoptr", skip::OK);
|
||
auto ctype = get_attribute(node, AT_CTYPE);
|
||
|
||
std::string deftype;
|
||
TypeInfo tinfo;
|
||
auto tnode = node.get_child(EL_TYPE);
|
||
auto btype = get_name(tnode);
|
||
parse_typeinfo(btype, tinfo);
|
||
if (tinfo.flags & TYPE_BASIC) {
|
||
// if typedef refers to a basic type, let's refer here to (global
|
||
// scope) C type (this is mostly the case, e.g. GstClockTime, etc)
|
||
deftype = GI_SCOPE + ctype;
|
||
} else if (tinfo.flags) {
|
||
assert(tinfo.cpptype.size());
|
||
// otherwise we alias to the corresponding wrapped type
|
||
// (e.g. GtkAllocation)
|
||
deftype = tinfo.cpptype;
|
||
}
|
||
auto aliasfmt = "typedef {} {};\n";
|
||
if (deftype.size())
|
||
out << fmt::format(aliasfmt, deftype, name);
|
||
// also mind ref type
|
||
if (tinfo.flags & TYPE_BOXED) {
|
||
assert(deftype.size());
|
||
out << fmt::format(
|
||
aliasfmt, deftype + GI_SUFFIX_REF, name + GI_SUFFIX_REF);
|
||
}
|
||
out << std::endl;
|
||
}
|
||
|
||
void process_element_enum(const pt::ptree::value_type &n, std::ostream &out,
|
||
std::ostream *_out_impl) const
|
||
{
|
||
auto &kind = n.first;
|
||
auto &node = n.second;
|
||
std::ostringstream oss;
|
||
auto name = get_name(node);
|
||
auto ctype = get_attribute(node, AT_CTYPE);
|
||
|
||
if (_out_impl) {
|
||
auto &out_impl = *_out_impl;
|
||
// need to generate namespaced function members
|
||
std::ostringstream oss_decl;
|
||
std::ostringstream oss_impl;
|
||
DepsSet dummy;
|
||
for (const auto &n : node) {
|
||
if (n.first == EL_FUNCTION) {
|
||
process_element_function(n, oss_decl, oss_impl, "", "", dummy);
|
||
}
|
||
}
|
||
if (oss_decl.tellp()) {
|
||
auto enumns = name + "NS_";
|
||
NamespaceGuard ns_d(out);
|
||
ns_d.push(enumns, false);
|
||
out << oss_decl.str();
|
||
|
||
NamespaceGuard ns_i(out_impl);
|
||
ns_i.push(enumns, false);
|
||
out_impl << oss_impl.str();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// enum name might end up matching (uppercase) define
|
||
name = unreserve(name);
|
||
|
||
// otherwise generate enum
|
||
NamespaceGuard nsg(oss);
|
||
nsg.push(ns);
|
||
// auto-adjust underlying type to avoid warnings;
|
||
// outside the range of underlying type ‘int’
|
||
// (no forward declare of enum in C, so type should be complete)
|
||
oss << fmt::format(
|
||
"enum class {} : std::underlying_type<{}>::type {{\n", name, ctype);
|
||
std::set<std::string> names;
|
||
for (const auto &n : node) {
|
||
if (n.first == EL_MEMBER) {
|
||
auto value = get_attribute(n.second, AT_CIDENTIFIER);
|
||
auto name = get_name(n.second);
|
||
// some enums/flags declare different names with same value
|
||
// which seems to confuse gobject-introspection
|
||
// (e.g. GstVideoFrameFlags, GstVideoBufferFlags, etc)
|
||
// and then they end up with repeated/duplicate name in GIR
|
||
// so let's only pick the first one of those
|
||
if (!std::get<1>(names.insert(name))) {
|
||
logger(Log::WARNING, "{} skipping duplicate {}", ctype, value);
|
||
continue;
|
||
}
|
||
oss << indent
|
||
<< fmt::format("{} = {},\n", unreserve(toupper(name)), value);
|
||
}
|
||
}
|
||
oss << "};\n\n";
|
||
// add operators in case of flag = bitfield
|
||
if (kind == EL_FLAGS)
|
||
oss << fmt::format("GI_FLAG_OPERATORS({})\n\n", name);
|
||
nsg.pop();
|
||
|
||
// declare ctype info
|
||
nsg.push(GI_REPOSITORY_NS);
|
||
name = ns + "::" + name;
|
||
oss << make_declare(true, name, ctype) << std::endl;
|
||
oss << make_declare(false, name, ctype) << std::endl;
|
||
oss << std::endl;
|
||
|
||
// declare gtype info
|
||
auto gtype = get_attribute(node, AT_GLIB_GET_TYPE, "");
|
||
if (gtype.size()) {
|
||
oss << fmt::format("template<> struct declare_gtype_of<{}>\n"
|
||
"{{ static GType get_type() {{ return {}(); }} }};",
|
||
name, gtype)
|
||
<< std::endl;
|
||
oss << std::endl;
|
||
}
|
||
|
||
// in case of a flag = bitfield, also mark it as such
|
||
if (kind == EL_FLAGS) {
|
||
oss << fmt::format(
|
||
"template<> struct is_bitfield<{}> : public std::true_type\n"
|
||
"{{}};",
|
||
name)
|
||
<< std::endl;
|
||
oss << std::endl;
|
||
}
|
||
|
||
oss << std::endl;
|
||
nsg.pop();
|
||
out << oss.str() << std::endl;
|
||
}
|
||
|
||
void process_element_const(const pt::ptree &node, std::ostream &out) const
|
||
{
|
||
auto name = get_name(node);
|
||
std::ostringstream oss;
|
||
try {
|
||
ArgInfo tinfo = parse_arginfo(node);
|
||
auto cpptype = tinfo.cpptype;
|
||
|
||
auto value = get_attribute(node, AT_CTYPE);
|
||
std::string cast;
|
||
std::string namesuffix;
|
||
if (tinfo.flags & TYPE_BASIC) {
|
||
if (tinfo.flags & TYPE_CLASS) {
|
||
// special string case
|
||
// need some suffix to make the variable (pointer) const
|
||
cpptype = "gchar";
|
||
namesuffix = "[]";
|
||
} else {
|
||
// normalize back to C (e.g. char*)
|
||
cpptype = tinfo.ctype;
|
||
}
|
||
} else {
|
||
// need to cast to enum type (flags/enum)
|
||
if (tinfo.flags & TYPE_ENUM) {
|
||
cast = fmt::format("({}) ", cpptype);
|
||
} else if (!(tinfo.flags & TYPE_VALUE)) {
|
||
throw skip("constant type " + cpptype + " not supported");
|
||
}
|
||
// accept alias typedef
|
||
}
|
||
// avoid clashes with existing defines
|
||
name = unreserve(name);
|
||
// NOTE what about an inline var in C++17
|
||
// recall that const implies static in C++
|
||
oss << fmt::format("GI_MODULE_INLINE const {} {}{} = {}{};", cpptype,
|
||
name, namesuffix, cast, value);
|
||
} catch (skip &ex) {
|
||
oss << "// SKIP constant " << name << "; " << ex.what();
|
||
}
|
||
out << oss.str() << std::endl << std::endl;
|
||
}
|
||
|
||
void process_element_property(const pt::ptree::value_type &entry,
|
||
std::ostream &out, std::ostream &impl, const std::string &klass,
|
||
DepsSet &deps) const
|
||
{
|
||
auto &node = entry.second;
|
||
|
||
ArgInfo tinfo;
|
||
auto name = get_name(node);
|
||
auto read = get_attribute<int>(node, AT_READABLE, 1);
|
||
auto write = get_attribute<int>(node, AT_WRITABLE, 1);
|
||
tinfo = parse_arginfo(node);
|
||
|
||
auto &&cpptype = tinfo.cpptype;
|
||
if (!tinfo.flags)
|
||
throw skip("unknown type " + cpptype);
|
||
// let's not go here for now
|
||
if (tinfo.flags & TYPE_CONTAINER)
|
||
throw skip("container property not supported", skip::TODO);
|
||
|
||
track_dependency(deps, tinfo);
|
||
|
||
// directly write all in decl
|
||
auto &oss = out;
|
||
(void)impl;
|
||
|
||
auto decl_name = name;
|
||
std::replace(decl_name.begin(), decl_name.end(), '-', '_');
|
||
std::string proptype = "property_proxy";
|
||
if (!read) {
|
||
proptype = "property_proxy_write";
|
||
} else if (!write) {
|
||
proptype = "property_proxy_read";
|
||
}
|
||
proptype = GI_NS_SCOPED + proptype;
|
||
|
||
auto qklass = qualify(klass, TYPE_OBJECT);
|
||
if (write)
|
||
oss << fmt::format("{3}<{0}, {4}> property_{2}()\n"
|
||
"{{ return {3}<{0}, {4}> (*this, \"{1}\"); }}",
|
||
cpptype, name, decl_name, proptype, qklass)
|
||
<< std::endl;
|
||
if (read)
|
||
oss << fmt::format("const {3}<{0}, {4}> property_{2}() const\n"
|
||
"{{ return {3}<{0}, {4}> (*this, \"{1}\"); }}",
|
||
cpptype, name, decl_name, proptype, qklass)
|
||
<< std::endl;
|
||
oss << std::endl;
|
||
}
|
||
|
||
void process_element_field(const pt::ptree::value_type &entry,
|
||
std::ostream &out, std::ostream &impl, const std::string &klass,
|
||
const std::string &klasstype, DepsSet &deps) const
|
||
{
|
||
auto &node = entry.second;
|
||
|
||
auto name = get_name(node);
|
||
auto readable = get_attribute<int>(node, AT_READABLE, 1);
|
||
auto writable = get_attribute<int>(node, AT_WRITABLE, 1);
|
||
auto priv = get_attribute<int>(node, AT_PRIVATE, 0);
|
||
|
||
ArgInfo tinfo;
|
||
try {
|
||
tinfo = parse_arginfo(node);
|
||
|
||
// only very plain basic fields
|
||
// type must be known
|
||
// no private
|
||
// no int* cookies or other strange things
|
||
if (!tinfo.flags || priv ||
|
||
get_pointer_depth(tinfo.ctype) != tinfo.pdepth ||
|
||
is_volatile(tinfo.ctype))
|
||
return;
|
||
} catch (...) {
|
||
// simply fail silently here and never mind
|
||
return;
|
||
}
|
||
|
||
if (!(tinfo.flags & (TYPE_VALUE | TYPE_CLASS)))
|
||
return;
|
||
|
||
// unconditionally reserve this to avoid clash with non-field member
|
||
ElementFunction func;
|
||
func.kind = EL_METHOD;
|
||
func.name = unreserve(name, true);
|
||
func.c_id = klasstype + "::" + name;
|
||
|
||
Parameter instance;
|
||
// real one, no base
|
||
parse_typeinfo(klasstype, instance.tinfo);
|
||
instance.instance = true;
|
||
instance.name = "obj";
|
||
instance.direction = DIR_IN;
|
||
|
||
// field parameter
|
||
Parameter param;
|
||
param.tinfo = tinfo;
|
||
param.name = "_value";
|
||
param.transfer = TRANSFER_NOTHING;
|
||
|
||
// common helper lambda for below
|
||
auto make_wrapper = [&](const std::string &funcname,
|
||
const std::string funcdef,
|
||
const std::vector<Parameter> ¶ms) {
|
||
func.functionexp = funcname;
|
||
// buffer result
|
||
std::ostringstream oss;
|
||
auto def = ::process_element_function(
|
||
ctx, ns, func, params, out, oss, klass, klasstype, deps);
|
||
// now write all if wrapping accessor did not fail
|
||
if (def.name.size())
|
||
impl << funcdef << std::endl << oss.str();
|
||
};
|
||
|
||
// a static helper is used in the following,
|
||
// rather than a compact local lambda,
|
||
// as the latter does not always cast to any function type
|
||
// also, internal casts are applied in the helper to handle enum/int
|
||
// and pointer type conversions
|
||
|
||
// also, the ctype info is derived from the cpptype as with func
|
||
// argument due to either
|
||
// + ctype missing, e.g. enum,
|
||
// + or minor discrepancies, e.g. gpointer vs gconstpointer
|
||
|
||
if (readable) {
|
||
param.direction = DIR_RETURN;
|
||
param.tinfo.ctype = make_ctype(param.tinfo, param.direction, false);
|
||
instance.tinfo.ctype = fmt::format("const {}*", instance.tinfo.dtype);
|
||
// define helper function
|
||
auto funcname = "_field_" + name + "_get";
|
||
auto funcdef = fmt::format(
|
||
"GI_MODULE_STATIC_OR_INLINE {} {} ({} {}) {{ return ({}) obj->{}; }}",
|
||
param.tinfo.ctype, funcname, instance.tinfo.ctype, instance.name,
|
||
param.tinfo.ctype, name);
|
||
make_wrapper(funcname, funcdef, {param, instance});
|
||
}
|
||
|
||
// not safe to assume any particular ownership of some struct field
|
||
// so let's not meddle with it other than the very basic cases
|
||
if (writable && (tinfo.flags & TYPE_VALUE)) {
|
||
param.direction = DIR_IN;
|
||
param.tinfo.ctype = make_ctype(param.tinfo, param.direction, false);
|
||
instance.tinfo.ctype = instance.tinfo.dtype + "*";
|
||
// define helper function
|
||
auto funcname = "_field_" + name + "_set";
|
||
auto funcdef =
|
||
fmt::format("GI_MODULE_STATIC_OR_INLINE void {} ({} {}, {} {}) {{ "
|
||
"obj->{} = (decltype(obj->{})) {}; }}",
|
||
funcname, instance.tinfo.ctype, instance.name, param.tinfo.ctype,
|
||
param.name, name, name, param.name);
|
||
// void return
|
||
Parameter vparam;
|
||
parse_typeinfo(GIR_VOID, vparam.tinfo);
|
||
vparam.direction = DIR_RETURN;
|
||
make_wrapper(funcname, funcdef, {vparam, param, instance});
|
||
}
|
||
}
|
||
|
||
FunctionDefinition process_element_function(
|
||
const pt::ptree::value_type &entry, std::ostream &out, std::ostream &impl,
|
||
const std::string &klass, const std::string &klasstype, DepsSet &deps,
|
||
std::ostream *call_args = nullptr) const
|
||
{
|
||
return ::process_element_function(ctx, ns, entry, out, impl, klass,
|
||
klasstype, deps, call_args, allow_deprecated_);
|
||
}
|
||
|
||
// unqualify (current ns qualifed) type
|
||
std::string unqualify(const std::string &name) const
|
||
{
|
||
return std::regex_replace(name, re_unqualify_, "");
|
||
}
|
||
|
||
static std::string get_record_filename(const std::string &rname, bool impl)
|
||
{
|
||
return tolower(rname) + (impl ? "_impl" : "") + ".hpp";
|
||
}
|
||
|
||
static std::string make_include(const std::string &hname, bool local)
|
||
{
|
||
if (hname.empty())
|
||
return hname;
|
||
char open = local ? '"' : '<';
|
||
char close = local ? '"' : '>';
|
||
std::ostringstream oss;
|
||
oss << "#include " << open << hname << close;
|
||
return oss.str();
|
||
}
|
||
|
||
// make include for a qualified dependency
|
||
// (not in current namespace)
|
||
std::string make_dep_include(const std::string &girname) const
|
||
{
|
||
auto lname = unqualify(girname);
|
||
return lname.size() && !is_qualified(lname)
|
||
? make_include(get_record_filename(lname, false), true) + '\n'
|
||
: EMPTY;
|
||
}
|
||
|
||
std::string make_dep_declare(const DepsSet &deps, bool only_ns = false) const
|
||
{
|
||
std::ostringstream oss;
|
||
NamespaceGuard nsg(oss);
|
||
std::string last_ns;
|
||
for (const auto &de : deps) {
|
||
if (last_ns != de.first) {
|
||
oss << std::endl;
|
||
nsg.pop();
|
||
last_ns = de.first;
|
||
if (!last_ns.empty())
|
||
nsg.push(last_ns, false);
|
||
}
|
||
if (only_ns && last_ns.empty())
|
||
continue;
|
||
const auto &d = de.second;
|
||
if (d.size() > 7 && (d[6] == ' ' || d[7] == ' ')) {
|
||
// there is a space in there, so it is not just a type name
|
||
// but rather struct/class XXX (a CallArgs case)
|
||
oss << d << ';' << std::endl;
|
||
continue;
|
||
}
|
||
auto c = unqualify(d);
|
||
if (!is_qualified(c))
|
||
oss << "class " << c << ";" << std::endl;
|
||
}
|
||
// apparently guard destructor runs too late
|
||
nsg.pop();
|
||
return oss.str();
|
||
}
|
||
|
||
static std::string make_conditional_include(
|
||
const std::string &hname, bool local)
|
||
{
|
||
if (hname.empty())
|
||
return hname;
|
||
|
||
auto templ = R"|(
|
||
#if defined(__has_include)
|
||
#if __has_include({}{}{})
|
||
#include {}{}{}
|
||
#endif
|
||
#endif
|
||
)|";
|
||
char open = local ? '"' : '<';
|
||
char close = local ? '"' : '>';
|
||
return fmt::format(templ, open, hname, close, open, hname, close);
|
||
}
|
||
|
||
static constexpr const char *const CLASS_PLACEHOLDER{"CLASS_PLACEHOLDER"};
|
||
|
||
// minor convencience helper type
|
||
struct TypeClassInfo
|
||
{
|
||
// (optionally) gir qualified
|
||
std::string parentgir;
|
||
// info on type-struct
|
||
TypeInfo ti;
|
||
};
|
||
|
||
TypeClassInfo collect_type_class_info(const std::string &girname) const
|
||
{
|
||
// class struct is skipped above,
|
||
// but we do look for it here to find *Class/*Interface struct
|
||
TypeClassInfo result;
|
||
auto &repo = ctx.repo;
|
||
auto &node = repo.tree(girname).second;
|
||
result.parentgir = get_attribute(node, AT_PARENT, "");
|
||
// NOTE parent might be in different ns
|
||
if (result.parentgir.size())
|
||
result.parentgir = repo.qualify(result.parentgir, girname);
|
||
auto cpptype = get_attribute(node, AT_GLIB_TYPE_STRUCT, "");
|
||
if (cpptype.empty())
|
||
throw skip(girname + " missing type-struct info");
|
||
cpptype = repo.qualify(cpptype, girname);
|
||
parse_typeinfo(cpptype, result.ti);
|
||
// more useful in this setting
|
||
result.ti.cpptype = unqualify(result.ti.cpptype);
|
||
if (result.ti.dtype.empty())
|
||
throw skip(girname + " missing C type-struct info");
|
||
return result;
|
||
}
|
||
|
||
typedef std::vector<std::pair<std::string, FunctionDefinition>>
|
||
VirtualMethods;
|
||
void process_element_record_class(const pt::ptree::value_type &entry,
|
||
const std::vector<TypeInfo> &interfaces, const std::string &decl,
|
||
const std::string &impl, const VirtualMethods &methods,
|
||
std::ostream &out_decl, std::ostream &out_impl) const
|
||
{
|
||
auto &node = entry.second;
|
||
auto name = get_name(node);
|
||
bool interface = (entry.first == EL_INTERFACE);
|
||
|
||
// run up parent hierarchy to check if all those can be properly
|
||
// generated
|
||
auto class_info = collect_type_class_info(name);
|
||
TypeClassInfo parent_class_info;
|
||
// no parent for interface
|
||
if (class_info.parentgir.size()) {
|
||
parent_class_info = collect_type_class_info(class_info.parentgir);
|
||
auto rec_info = parent_class_info;
|
||
while (rec_info.parentgir.size())
|
||
rec_info = collect_type_class_info(rec_info.parentgir);
|
||
}
|
||
|
||
// collect ok interfaces
|
||
std::vector<TypeInfo> itfs_info;
|
||
for (auto &&itf : interfaces) {
|
||
try {
|
||
itfs_info.emplace_back(collect_type_class_info(itf.girname).ti);
|
||
// base class needs to be declared
|
||
out_decl << make_dep_include(itf.girname);
|
||
} catch (...) {
|
||
}
|
||
}
|
||
out_decl << std::endl;
|
||
|
||
// put into inner namespace to avoid name clash
|
||
// declaration part
|
||
NamespaceGuard ns_decl(out_decl);
|
||
ns_decl.push(ns);
|
||
|
||
ns_decl.push(GI_NS_IMPL);
|
||
ns_decl.push(GI_NS_INTERNAL);
|
||
|
||
// class definition
|
||
// explicitly specify a non-public non-virtual destructor
|
||
auto def_templ = R"|(
|
||
class {0}
|
||
{{
|
||
typedef {0} self;
|
||
public:
|
||
typedef {1} instance_type;
|
||
typedef {2} {3}_type;
|
||
|
||
{6}
|
||
struct TypeInitData;
|
||
|
||
protected:
|
||
{5} ~{0}() = default;
|
||
static {5} void {3}_init (gpointer {3}_struct, gpointer );
|
||
|
||
{4}
|
||
}};
|
||
|
||
)|";
|
||
|
||
std::string kind = interface ? "interface" : "class";
|
||
const auto klassnamedef = class_info.ti.cpptype + "Def";
|
||
// used for interfaces
|
||
const std::string suffix_class_impl = "ClassImpl";
|
||
const auto klassname =
|
||
class_info.ti.cpptype + (interface ? suffix_class_impl : "");
|
||
|
||
// conflict check and type init lists
|
||
std::string conflict_check, type_init, type_init_calc;
|
||
conflict_check.reserve(1024);
|
||
type_init.reserve(1024);
|
||
type_init_calc.reserve(1024);
|
||
std::string calc_indent = indent + indent;
|
||
for (auto &&method : methods) {
|
||
auto &&n = method.first;
|
||
conflict_check +=
|
||
fmt::format("using GI_MEMBER_CHECK_CONFLICT({}) = self;\n", n);
|
||
type_init +=
|
||
fmt::format("{}GI_MEMBER_DEFINE({}, {})\n", indent, klassname, n);
|
||
type_init_calc +=
|
||
fmt::format("{}{}GI_MEMBER_HAS_DEFINITION(SubClass, DefData, {})",
|
||
type_init_calc.empty() ? "" : ",\n", calc_indent, n);
|
||
}
|
||
|
||
// (note that e.g. xlib cases prefix might not help, or ns interference)
|
||
// drop inline mark on pure virtual interface functions;
|
||
// does not quite make sense and might otherwise lead to compiler
|
||
// warnings
|
||
static const std::regex re_inline(GI_INLINE + ' ', std::regex::optimize);
|
||
out_decl << fmt::format(def_templ, klassnamedef, qualify(name, TYPE_OBJECT),
|
||
class_info.ti.dtype, kind, std::regex_replace(decl, re_inline, ""),
|
||
GI_INLINE, conflict_check);
|
||
if (interface)
|
||
out_decl << fmt::format("using {}Impl = detail::InterfaceImpl<{}>;", name,
|
||
klassnamedef)
|
||
<< std::endl;
|
||
|
||
// avoid ambiguous unqualified name lookup of friend class below
|
||
// (as the scopes of parent classes are involved then as well)
|
||
auto class_templ = R"|(
|
||
class {0}: public {1}
|
||
{{
|
||
friend class internal::{4};
|
||
typedef {0} self;
|
||
typedef {1} super;
|
||
|
||
protected:
|
||
using super::super;
|
||
{2}
|
||
{3}
|
||
}};
|
||
|
||
)|";
|
||
|
||
auto classextra = R"|(
|
||
private:
|
||
// make local helpers private
|
||
using super::get_struct_;
|
||
using super::gobj_;
|
||
|
||
protected:
|
||
// disambiguation helper types
|
||
{}
|
||
)|";
|
||
|
||
// qualification helper; ensure impl::internal qualified
|
||
// (also adds current ns if needed)
|
||
auto implqualify = [&](const std::string &cpptype) {
|
||
auto result = cpptype;
|
||
auto pos = result.find(GI_SCOPE.c_str());
|
||
auto insert = GI_SCOPE + GI_NS_IMPL + GI_SCOPE + GI_NS_INTERNAL;
|
||
if (pos != result.npos) {
|
||
result.insert(pos, insert);
|
||
} else {
|
||
insert = ns + insert + GI_SCOPE;
|
||
result.insert(0, insert);
|
||
}
|
||
return result;
|
||
};
|
||
|
||
// collect interface types and helper types
|
||
std::ostringstream oss_types;
|
||
std::vector<std::string> itfs;
|
||
for (auto &&itf : itfs_info) {
|
||
auto kname = itf.cpptype + suffix_class_impl;
|
||
kname = implqualify(kname);
|
||
itfs.push_back(kname);
|
||
// fall back to ctype as it needs to be fully qualified
|
||
static const std::regex re_descope(GI_SCOPE, std::regex::optimize);
|
||
auto tprefix = std::regex_replace(itf.dtype, re_descope, "");
|
||
oss_types << fmt::format("typedef {} {}_type;\n", kname, tprefix);
|
||
}
|
||
auto extra = interface ? EMPTY : fmt::format(classextra, oss_types.str());
|
||
// determine baseclasses
|
||
// qualified klassnamedef to avoid ambiguous parent class lookup
|
||
// (when used as a typedef within class)
|
||
std::string superclass =
|
||
interface ? fmt::format("detail::InterfaceClassImpl<{}>", name + "Impl")
|
||
: fmt::format("detail::ClassTemplate<{}, {}{}{}>",
|
||
implqualify(klassnamedef),
|
||
implqualify(parent_class_info.ti.cpptype),
|
||
itfs.size() ? ", " : "",
|
||
boost::algorithm::join(itfs, ", "));
|
||
static const std::regex re_virtual("virtual ", std::regex::optimize);
|
||
static const std::regex re_pure("= 0", std::regex::optimize);
|
||
auto idecl = std::regex_replace(decl, re_virtual, "");
|
||
idecl = std::regex_replace(idecl, re_pure, "override");
|
||
// add macro for optional warning suppression
|
||
if (!interface)
|
||
out_decl << GI_CLASS_IMPL_BEGIN << std::endl << std::endl;
|
||
out_decl << fmt::format(
|
||
class_templ, klassname, superclass, extra, idecl, klassnamedef);
|
||
|
||
// type init
|
||
// at this later stage as it contains template definitions
|
||
// that refer to the above class
|
||
auto type_init_templ = R"|(
|
||
struct {0}::TypeInitData
|
||
{{
|
||
{1}
|
||
template<typename SubClass>
|
||
constexpr static TypeInitData factory()
|
||
{{
|
||
{3}using DefData = detail::DefinitionData<SubClass, TypeInitData>;
|
||
return {{
|
||
{2}
|
||
}};
|
||
}}
|
||
}};
|
||
)|";
|
||
out_decl << fmt::format(type_init_templ, klassnamedef, type_init,
|
||
type_init_calc, methods.empty() ? "// " : "");
|
||
// end internal ns
|
||
ns_decl.pop(1);
|
||
|
||
if (!interface)
|
||
out_decl << GI_CLASS_IMPL_END << std::endl
|
||
<< std::endl
|
||
<< fmt::format("using {}Impl = detail::ObjectImpl<{}, {}::{}>;",
|
||
name, name, GI_NS_INTERNAL, klassname)
|
||
<< std::endl
|
||
<< std::endl;
|
||
|
||
// implementation part
|
||
NamespaceGuard ns_impl(out_impl);
|
||
ns_impl.push(ns);
|
||
|
||
ns_impl.push(GI_NS_IMPL);
|
||
ns_impl.push(GI_NS_INTERNAL);
|
||
|
||
auto &ctype = class_info.ti.dtype;
|
||
out_impl << fmt::format(
|
||
"void {}::{}_init (gpointer {}_struct, gpointer factory)\n{{\n",
|
||
klassnamedef, kind, kind);
|
||
out_impl << indent
|
||
<< fmt::format(
|
||
"{} *methods = ({} *) {}_struct;\n", ctype, ctype, kind);
|
||
// avoid warning if no methods
|
||
out_impl << indent << "(void) methods;" << std::endl << std::endl;
|
||
// init data from factory
|
||
out_impl
|
||
<< indent
|
||
<< "auto init_data = GI_MEMBER_INIT_DATA(TypeInitData, factory);\n";
|
||
out_impl << indent << "(void) init_data;" << std::endl << std::endl;
|
||
|
||
for (auto &&method : methods) {
|
||
auto &&n = method.first;
|
||
auto &&def = method.second;
|
||
std::vector<std::string> args, transfers;
|
||
for (auto &&arg : def.cpp_decl)
|
||
args.push_back(arg.second);
|
||
auto cpp_return =
|
||
def.cpp_outputs.size() ? def.cpp_outputs[0].type : CPP_VOID;
|
||
auto sig = fmt::format(
|
||
"{} (*) ({})", cpp_return, boost::algorithm::join(args, ", "));
|
||
std::string transferargs, precheck;
|
||
if (def.arg_traits.empty()) {
|
||
// only arrange to call the raw methods
|
||
// if new-style detection (or explicit specification) is in place
|
||
precheck = " && factory";
|
||
transferargs = "std::nullptr_t";
|
||
} else {
|
||
transferargs = make_arg_traits(def.arg_traits, def.c_sig);
|
||
}
|
||
// add a hard cast to deal with const differences (e.g. string vs
|
||
// const char*)
|
||
out_impl << indent
|
||
<< fmt::format("if (init_data.{0}{3}) methods->{0} = (decltype "
|
||
"(methods->{0})) "
|
||
"gi::detail::method_wrapper<self, {1}, "
|
||
"{2}>::wrapper<&self::{0}_>;",
|
||
n, sig, transferargs, precheck)
|
||
<< std::endl;
|
||
}
|
||
out_impl << "}" << std::endl << std::endl;
|
||
|
||
auto iimpl =
|
||
std::regex_replace(impl, std::regex(CLASS_PLACEHOLDER), klassname);
|
||
out_impl << iimpl;
|
||
}
|
||
|
||
std::vector<TypeInfo> record_collect_interfaces(
|
||
const pt::ptree::value_type &entry, const TypeInfo ¤t,
|
||
const TypeInfo &parent, DepsSet &deps) const
|
||
{
|
||
auto &repo = ctx.repo;
|
||
auto &node = entry.second;
|
||
auto name = get_name(node);
|
||
|
||
// listed interfaces also include parent's interfaces
|
||
// so we should subtract those for good measure
|
||
|
||
auto collect_interfaces = [&](const pt::ptree &node,
|
||
const std::string &basename) {
|
||
std::set<std::string> result;
|
||
auto p = node.equal_range(EL_IMPLEMENTS);
|
||
assert(basename.size());
|
||
for (auto &q = p.first; q != p.second; ++q) {
|
||
auto n = get_name(q->second, std::nothrow);
|
||
if (n.size()) {
|
||
result.insert(repo.qualify(n, basename));
|
||
}
|
||
}
|
||
return result;
|
||
};
|
||
|
||
std::vector<std::string> itfs;
|
||
// ensure all GIR names fully qualified
|
||
auto itfs_local = collect_interfaces(node, current.girname);
|
||
if (parent.girname.size()) {
|
||
// qualify relative to parent
|
||
auto itfs_parent =
|
||
collect_interfaces(repo.tree(parent.girname).second, parent.girname);
|
||
// only keep those not matching a parent's interface (both fully
|
||
// qualified)
|
||
for (auto &&n : itfs_local) {
|
||
if (itfs_parent.find(n) == itfs_parent.end())
|
||
itfs.push_back(n);
|
||
}
|
||
} else {
|
||
std::copy(itfs_local.begin(), itfs_local.end(), std::back_inserter(itfs));
|
||
}
|
||
|
||
std::vector<TypeInfo> interfaces;
|
||
for (auto &n : itfs) {
|
||
TypeInfo itf;
|
||
parse_typeinfo(n, itf);
|
||
// should know about this interface by now
|
||
// but let's continue anyway
|
||
if (!itf.flags) {
|
||
logger(Log::WARNING, "{} implements unknown interface {}", name, n);
|
||
continue;
|
||
}
|
||
deps.insert({"", itf.cpptype});
|
||
interfaces.push_back(itf);
|
||
}
|
||
|
||
return interfaces;
|
||
}
|
||
|
||
// returns (decl header name, impl header name)
|
||
std::tuple<std::string, std::string> process_element_record(
|
||
const pt::ptree::value_type &entry, bool onlyverify,
|
||
std::ostream *call_args = nullptr) const
|
||
{
|
||
auto &kind = entry.first;
|
||
auto &node = entry.second;
|
||
auto name = get_name(node);
|
||
|
||
logger(Log::LOG, "checking {} {} {}", kind, name, onlyverify);
|
||
|
||
TypeInfo current;
|
||
parse_typeinfo(name, current);
|
||
// only consider real type, but keep info around
|
||
if (!current.flags)
|
||
return std::make_tuple("", "");
|
||
auto &ctype = current.dtype;
|
||
// could happen for a GType only case (e.g. GstFraction)
|
||
if (!ctype.size())
|
||
throw skip("no c:type info");
|
||
bool is_object_base = (current.girname == GIR_GOBJECT);
|
||
|
||
// check parent
|
||
TypeInfo parent;
|
||
auto &repo = ctx.repo;
|
||
// 1 special case here
|
||
if (kind == EL_OBJECT && !is_object_base) {
|
||
auto parentgir = get_attribute(node, AT_PARENT, "");
|
||
if (parentgir.empty())
|
||
throw skip("missing parent class");
|
||
parse_typeinfo(parentgir, parent);
|
||
if (!parent.flags)
|
||
throw skip("unknown parent " + parentgir);
|
||
// parent might be found, but might have problems of its own
|
||
// so that needs to be checked recursively
|
||
// (at least if within current ns)
|
||
if (onlyverify && !is_qualified(parentgir)) {
|
||
try {
|
||
process_element_record(repo.tree(parentgir), onlyverify);
|
||
} catch (const skip &ex) {
|
||
throw skip(std::string("parent problem; ") + ex.what());
|
||
}
|
||
}
|
||
}
|
||
|
||
if (onlyverify || is_object_base)
|
||
return std::make_tuple("", "");
|
||
|
||
// no throwing or giving up after this point
|
||
// we have now committed to coming up with a class
|
||
// declaration/definition (even if it is a pretty empty one)
|
||
logger(Log::LOG, "processing {} {}", kind, name);
|
||
|
||
// generate classes in subnamespace
|
||
const std::string nsbase("base");
|
||
auto namebase = name + "Base";
|
||
// avoid qualification later on with standard ns (e.g. Gst)
|
||
auto qnamebase = nsbase + "::" + namebase;
|
||
|
||
// collect output of members and their dependencies
|
||
std::ostringstream oss_decl, oss_class_decl;
|
||
std::ostringstream oss_impl, oss_class_impl;
|
||
VirtualMethods vmethods;
|
||
DepsSet deps;
|
||
for (const auto &n : node) {
|
||
auto el = n.first;
|
||
int introspectable = get_attribute<int>(n.second, AT_INTROSPECTABLE, 1);
|
||
try {
|
||
// try even if marked introspectable
|
||
// some are useful in non-runtime setting
|
||
if (el == EL_FUNCTION || el == EL_CONSTRUCTOR || el == EL_METHOD ||
|
||
el == EL_SIGNAL) {
|
||
process_element_function(
|
||
n, oss_decl, oss_impl, qnamebase, name, deps, call_args);
|
||
} else if (el == EL_VIRTUAL_METHOD && introspectable &&
|
||
ctx.options.classimpl) {
|
||
// placeholder replaced suitably later on
|
||
auto def = process_element_function(n, oss_class_decl, oss_class_impl,
|
||
CLASS_PLACEHOLDER, name, deps, call_args);
|
||
if (def.name.size())
|
||
vmethods.push_back({def.name, def});
|
||
} else if (el == EL_FIELD && introspectable) {
|
||
process_element_field(n, oss_decl, oss_impl, qnamebase, name, deps);
|
||
} else if (el == EL_PROPERTY && introspectable) {
|
||
process_element_property(n, oss_decl, oss_impl, qnamebase, deps);
|
||
}
|
||
} catch (std::runtime_error &ex) {
|
||
handle_exception(n, ex);
|
||
}
|
||
}
|
||
|
||
// actual output to file
|
||
auto fname_decl = get_record_filename(name, false);
|
||
File out_decl(ns, fname_decl, false);
|
||
auto fname_impl = get_record_filename(name, true);
|
||
File out_impl(ns, fname_impl, false);
|
||
|
||
// a superclass needs full declaration (not only forward declaration)
|
||
// skip include for GObject, which would lead to object.hpp,
|
||
// which is not generated in GObject namespace, but rather provided by gi
|
||
// so it actually references gi/object.hpp, which is only works if gi/
|
||
// is part of the include path, which is a bit confusing/messy
|
||
// (as it is/should always be referenced by gi/xyz, e.g. gi/gi.hpp)
|
||
if (parent.girname != GIR_GOBJECT)
|
||
out_decl << make_dep_include(parent.girname);
|
||
out_decl << std::endl;
|
||
|
||
// implemented interfaces are also dependency
|
||
std::vector<TypeInfo> interfaces =
|
||
record_collect_interfaces(entry, current, parent, deps);
|
||
|
||
// namespace in decl before forward class decl
|
||
NamespaceGuard ns_decl(out_decl);
|
||
ns_decl.push(ns);
|
||
|
||
// all declarations are included prior to implementation
|
||
// forward class declarations in declaration
|
||
deps.erase({"", current.cpptype});
|
||
out_decl << make_dep_declare(deps);
|
||
out_decl << std::endl;
|
||
|
||
// also forward declare 'oneself' since only base is defined below
|
||
out_decl << "class " << name << ";" << std::endl;
|
||
out_decl << std::endl;
|
||
|
||
// namespace in impl following includes
|
||
NamespaceGuard ns_impl(out_impl);
|
||
ns_impl.push(ns);
|
||
|
||
// base class subnamespace
|
||
ns_decl.push(nsbase);
|
||
ns_impl.push(nsbase);
|
||
|
||
// class definition
|
||
auto obj_templ = R"|(
|
||
#define {3} {4}::{0}
|
||
class {0} : public {2}
|
||
{{
|
||
typedef {2} super_type;
|
||
public:
|
||
typedef {1} BaseObjectType;
|
||
|
||
{0} (std::nullptr_t = nullptr) : super_type() {{}}
|
||
|
||
BaseObjectType *gobj_() {{ return (BaseObjectType*) super_type::gobj_(); }}
|
||
const BaseObjectType *gobj_() const {{ return (const BaseObjectType*) super_type::gobj_(); }}
|
||
BaseObjectType *gobj_copy_() const {{ return (BaseObjectType*) super_type::gobj_copy_(); }}
|
||
|
||
)|";
|
||
|
||
auto boxed_templ = R"|(
|
||
#define {2} {3}::{0}
|
||
class {0} : public {1}
|
||
{{
|
||
typedef {1} super_type;
|
||
public:
|
||
|
||
{0} (std::nullptr_t = nullptr) : super_type() {{}}
|
||
|
||
)|";
|
||
|
||
auto gtype = get_attribute(node, AT_GLIB_GET_TYPE, "");
|
||
auto basedef = toupper(fmt::format("GI_{}_{}_BASE", ns, name));
|
||
// class definition
|
||
bool is_variant = false;
|
||
if (kind == EL_OBJECT) {
|
||
out_decl << fmt::format(
|
||
obj_templ, namebase, ctype, parent.cpptype, basedef, nsbase);
|
||
} else if (kind == EL_INTERFACE) {
|
||
out_decl << fmt::format(
|
||
obj_templ, namebase, ctype, "gi::InterfaceBase", basedef, nsbase);
|
||
} else {
|
||
auto tmpl = GI_NS_DETAIL_SCOPED +
|
||
(gtype.size() ? "GBoxedWrapperBase" : "CBoxedWrapperBase");
|
||
auto parent = fmt::format("{}<{}, {}>", tmpl, namebase, ctype);
|
||
// override if special fundamental boxed-like case
|
||
if ((is_variant = (current.girname == GIR_GVARIANT))) {
|
||
parent = "detail::VariantWrapper";
|
||
gtype.clear();
|
||
}
|
||
out_decl << fmt::format(boxed_templ, namebase, parent, basedef, nsbase);
|
||
}
|
||
|
||
if (gtype.size()) {
|
||
out_decl << fmt::format(
|
||
"static GType get_type_ () G_GNUC_CONST {{ return {}(); }} ", gtype);
|
||
out_decl << std::endl << std::endl;
|
||
}
|
||
|
||
// add some helpers to obtain known implemented interfaces
|
||
for (auto &&_itf : interfaces) {
|
||
// temp extend for wrapping below
|
||
ArgInfo itf;
|
||
(TypeInfo &)itf = _itf;
|
||
auto decl_fmt = "{0} {1}interface_ (gi::interface_tag<{0}>)";
|
||
auto impl_fmt = decl_fmt;
|
||
auto decl = fmt::format(decl_fmt, itf.cpptype, "");
|
||
out_decl << GI_INLINE << ' ' << decl << ";" << std::endl << std::endl;
|
||
// wrap a properly casted extra reference (ensure no sink'ing)
|
||
auto impl = fmt::format(impl_fmt, itf.cpptype, (namebase + "::"));
|
||
out_impl << impl << std::endl;
|
||
auto towrap =
|
||
fmt::format("({}::BaseObjectType*) gobj_copy_()", itf.cpptype);
|
||
auto w =
|
||
fmt::format(make_wrap_format(ArgInfo{itf}, TRANSFER_FULL), towrap);
|
||
out_impl << fmt::format("{{ return {}; }}", w) << std::endl << std::endl;
|
||
// conversion operator
|
||
auto op_decl = fmt::format("operator {} ()", itf.cpptype);
|
||
auto op_impl = fmt::format(
|
||
"{}::{}\n{{ return interface_ (gi::interface_tag<{}>()); }}",
|
||
namebase, op_decl, itf.cpptype);
|
||
out_decl << GI_INLINE << ' ' << op_decl << ';' << std::endl << std::endl;
|
||
out_impl << op_impl << std::endl << std::endl;
|
||
}
|
||
|
||
out_decl << oss_decl.str();
|
||
out_decl << "}; // class" << std::endl;
|
||
out_decl << std::endl;
|
||
ns_decl.pop();
|
||
|
||
out_impl << oss_impl.str();
|
||
out_impl << std::endl;
|
||
ns_impl.pop();
|
||
|
||
// optionally include supplements/overrides for this class
|
||
auto include_extra = [&](File &out, bool impl) {
|
||
for (auto &&suffix : {"_extra_def", "_extra"}) {
|
||
auto header =
|
||
(fs::path(tolower(ns)) / get_record_filename(name + suffix, impl))
|
||
.native();
|
||
out << make_conditional_include(header, false) << std::endl;
|
||
}
|
||
};
|
||
include_extra(out_decl, false);
|
||
include_extra(out_impl, true);
|
||
|
||
ns_decl.push(GI_REPOSITORY_NS);
|
||
// make fragment to define final class
|
||
{
|
||
NamespaceGuard nst(out_decl);
|
||
nst.push(ns, false);
|
||
auto supertype = basedef;
|
||
auto reftype = name + GI_SUFFIX_REF;
|
||
if (kind != EL_OBJECT && kind != EL_INTERFACE && !is_variant) {
|
||
// boxed base templates define a few members with CppType return type
|
||
// so in the CppTypeBase defined above that is still CppTypeBase
|
||
// again add the template as subclass but now with final CppType
|
||
auto tmpl = GI_NS_DETAIL_SCOPED +
|
||
(gtype.size() ? "GBoxedWrapper" : "CBoxedWrapper");
|
||
supertype = fmt::format(
|
||
"{}<{}, {}, {}, {}>", tmpl, name, ctype, basedef, reftype);
|
||
// forward declaration of corresponding ref type
|
||
out_decl << fmt::format("class {};", reftype) << std::endl << std::endl;
|
||
}
|
||
auto fmtclass =
|
||
"class {0} : public {1}\n"
|
||
"{{ typedef {1} super_type; using super_type::super_type; }};\n";
|
||
out_decl << fmt::format(fmtclass, name, supertype) << std::endl;
|
||
|
||
// in case of boxed, also define ref type
|
||
if (kind != EL_OBJECT && kind != EL_INTERFACE && !is_variant) {
|
||
auto tmpl = GI_NS_DETAIL_SCOPED +
|
||
(gtype.size() ? "GBoxedRefWrapper" : "CBoxedRefWrapper");
|
||
supertype = fmt::format("{}<{}, {}, {}>", tmpl, name, ctype, basedef);
|
||
out_decl << std::endl
|
||
<< fmt::format(fmtclass, reftype, supertype) << std::endl;
|
||
}
|
||
}
|
||
// GInitiallyUnowned is typedef'ed to GObject
|
||
// so we have to avoid duplicate definition
|
||
// FIXME generally no way to check for that using Gir, provide some
|
||
// override here ??
|
||
if (current.girname != GIR_GINITIALLYUNOWNED) {
|
||
// declare type info
|
||
out_decl << make_declare(false, ns + "::" + name, ctype) << std::endl;
|
||
out_decl << std::endl;
|
||
}
|
||
ns_decl.pop();
|
||
|
||
// now process the virtual method class parts
|
||
if (ctx.options.classimpl && (kind == EL_OBJECT || kind == EL_INTERFACE)) {
|
||
try {
|
||
process_element_record_class(entry, interfaces, oss_class_decl.str(),
|
||
oss_class_impl.str(), vmethods, out_decl, out_impl);
|
||
} catch (const skip &ex) {
|
||
logger(Log::WARNING, "skipping class generation for {}; {}", name,
|
||
ex.what());
|
||
}
|
||
}
|
||
|
||
return std::make_tuple(fname_decl, fname_impl);
|
||
}
|
||
|
||
std::string handle_exception(
|
||
const pt::ptree::value_type &n, const std::runtime_error &ex) const
|
||
{
|
||
auto &el = n.first;
|
||
const auto &name = get_name(n.second, std::nothrow);
|
||
auto ex_skip = dynamic_cast<const skip *>(&ex);
|
||
if (!ex_skip || ex_skip->cause == skip::INVALID) {
|
||
auto msg = fmt::format("{} {} {}; {}",
|
||
(ex_skip ? "skipping" : "EXCEPTION processing"), el, name, ex.what());
|
||
Log level = ex_skip ? Log::WARNING : Log::ERROR;
|
||
if (check_suppression(ns, el, name))
|
||
level = Log::DEBUG;
|
||
logger(level, msg);
|
||
} else {
|
||
logger(Log::DEBUG, "discarding {} {}; {}", el, name, ex.what());
|
||
}
|
||
return name;
|
||
}
|
||
|
||
typedef std::function<void(const pt::ptree::value_type &)> entry_processor;
|
||
void process_entries(const pt::ptree &node, const entry_processor &proc)
|
||
{
|
||
for (auto it = node.begin(); it != node.end(); ++it) {
|
||
auto &&n = *it;
|
||
if (n.first == PT_ATTR)
|
||
continue;
|
||
try {
|
||
proc(n);
|
||
} catch (std::runtime_error &ex) {
|
||
auto name = handle_exception(n, ex);
|
||
// in any case give up on this name
|
||
ctx.repo.discard(name);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool visit_ok(const pt::ptree::value_type &n) const
|
||
{
|
||
const auto &girname = get_name(n.second, std::nothrow);
|
||
auto ninfo = ctx.repo.lookup(girname);
|
||
if (ninfo && !(ninfo->info && (ninfo->info->flags & TYPE_PREDEFINED))) {
|
||
logger(Log::LOG, "visiting {} {}", n.first, girname);
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
const char *process_libs()
|
||
{
|
||
// also find needed shared libraries in case of dlopen
|
||
auto sl = get_attribute(tree_, AT_SHARED_LIBRARY, "");
|
||
std::vector<std::string> shlibs, qshlibs;
|
||
boost::split(shlibs, sl, boost::is_any_of(","));
|
||
for (auto &l : shlibs)
|
||
if (l.size())
|
||
qshlibs.push_back(fmt::format("\"{}\"", l));
|
||
|
||
auto h_libs = "_libs.hpp";
|
||
File libs(ns, h_libs);
|
||
|
||
auto libs_templ = R"|(
|
||
namespace internal {{
|
||
|
||
GI_INLINE_DECL std::vector<const char*> _libs()
|
||
{{ return {{{0}}}; }}
|
||
|
||
}} // internal
|
||
)|";
|
||
libs << fmt::format(libs_templ, boost::join(qshlibs, ","));
|
||
|
||
return h_libs;
|
||
}
|
||
|
||
public:
|
||
std::string process_tree(const std::vector<std::string> &dep_headers)
|
||
{
|
||
logger(Log::INFO, "processing namespace {} {}", ns, version_);
|
||
// set state for ns processing
|
||
ctx.repo.set_ns(ns);
|
||
File::set_root(ctx.options.rootdir);
|
||
|
||
// check if deprecated should pass for this ns
|
||
allow_deprecated_ = ctx.match_ignore.matches("deprecated", ns, {version_});
|
||
|
||
// optionally process libs
|
||
auto h_libs = ctx.options.dl ? process_libs() : "";
|
||
|
||
auto h_types = "_types.hpp";
|
||
File types(ns, h_types);
|
||
// index run
|
||
// collect info by name
|
||
// also gather alias/type info
|
||
entry_processor proc_index = [&](const pt::ptree::value_type &n) {
|
||
const auto &name = get_name(n.second, std::nothrow);
|
||
auto deprecated = get_attribute<int>(n.second, AT_DEPRECATED, 0);
|
||
if (deprecated && !allow_deprecated_)
|
||
return;
|
||
auto &el = n.first;
|
||
// redirect to oblivion
|
||
// empty name might originate from a glib:boxed with glib:name attribute
|
||
// discard those as well, as that is a glib type without C-type
|
||
// (which are not really useful in our situation)
|
||
if (name.empty() || ctx.match_ignore.matches(ns, el, {name})) {
|
||
logger(Log::INFO, "ignoring {} {}", el, name);
|
||
} else {
|
||
ctx.repo.add(name, n);
|
||
if (n.first == EL_ALIAS && visit_ok(n)) {
|
||
process_element_alias(n.second, types);
|
||
}
|
||
}
|
||
};
|
||
process_entries(tree_, proc_index);
|
||
|
||
// process basic types and callbacks
|
||
auto h_enums = "_enums.hpp";
|
||
auto h_flags = "_flags.hpp";
|
||
auto h_constants = "_constants.hpp";
|
||
auto h_callbacks = "_callbacks.hpp";
|
||
auto h_callbacks_impl = "_callbacks_impl.hpp";
|
||
auto h_functions = "_functions.hpp";
|
||
auto h_functions_impl = "_functions_impl.hpp";
|
||
auto h_call_args = "_callargs.hpp";
|
||
|
||
File enums(ns, h_enums, false);
|
||
File flags(ns, h_flags, false);
|
||
File constants(ns, h_constants);
|
||
File callbacks(ns, h_callbacks);
|
||
File callbacks_impl(ns, h_callbacks_impl);
|
||
File functions(ns, h_functions);
|
||
File functions_impl(ns, h_functions_impl);
|
||
|
||
// setup CallArgs handling
|
||
// the definition of a CallArgs requires complete types for fields
|
||
// so it will have to be among the last header file, collected as we go
|
||
// actual file handled below
|
||
std::ostringstream call_args;
|
||
|
||
entry_processor proc_pass_1 = [&](const pt::ptree::value_type &n) {
|
||
auto &el = n.first;
|
||
if (el == EL_ENUM && visit_ok(n)) {
|
||
process_element_enum(n, enums, nullptr);
|
||
} else if (el == EL_FLAGS && visit_ok(n)) {
|
||
process_element_enum(n, flags, nullptr);
|
||
} else if (el == EL_CONST && visit_ok(n)) {
|
||
process_element_const(n.second, constants);
|
||
}
|
||
};
|
||
process_entries(tree_, proc_pass_1);
|
||
|
||
// check class types we can handle and will provide a definition for
|
||
entry_processor proc_pass_class = [&](const pt::ptree::value_type &n) {
|
||
auto &el = n.first;
|
||
if ((el == EL_RECORD || el == EL_OBJECT || el == EL_INTERFACE) &&
|
||
visit_ok(n)) {
|
||
process_element_record(n, true, &call_args);
|
||
}
|
||
};
|
||
process_entries(tree_, proc_pass_class);
|
||
|
||
// so the known classes will be declared/defined
|
||
// check what callback typedefs that allows for
|
||
// check class types we can handle and will provide a definition for
|
||
DepsSet cb_deps;
|
||
std::ostringstream cb_decl;
|
||
entry_processor proc_pass_callbacks = [&](const pt::ptree::value_type &n) {
|
||
auto &el = n.first;
|
||
if (el == EL_CALLBACK && visit_ok(n)) {
|
||
std::ostringstream null;
|
||
process_element_function(n, cb_decl, callbacks_impl, "", "", cb_deps);
|
||
}
|
||
};
|
||
process_entries(tree_, proc_pass_callbacks);
|
||
// write callbacks
|
||
// need to declare deps first
|
||
callbacks << make_dep_declare(cb_deps);
|
||
callbacks << std::endl;
|
||
callbacks << cb_decl.str();
|
||
|
||
// now we know all supported types and supported callbacks
|
||
// pass over class types again and fill in
|
||
std::set<std::pair<std::string, std::string>> includes;
|
||
DepsSet functions_deps;
|
||
std::ostringstream functions_decl;
|
||
entry_processor proc_pass_2 = [&](const pt::ptree::value_type &n) {
|
||
auto &el = n.first;
|
||
if ((el == EL_RECORD || el == EL_OBJECT || el == EL_INTERFACE) &&
|
||
visit_ok(n)) {
|
||
auto &&res = process_element_record(n, false, &call_args);
|
||
includes.insert({std::get<0>(res), std::get<1>(res)});
|
||
} else if (el == EL_FUNCTION && visit_ok(n)) {
|
||
// all types known now, so include CallArgs directly
|
||
process_element_function(n, functions_decl, functions_impl, "", "",
|
||
functions_deps, &call_args);
|
||
} else if ((el == EL_ENUM || el == EL_FLAGS) && visit_ok(n)) {
|
||
process_element_enum(n, functions, &functions_impl);
|
||
}
|
||
};
|
||
process_entries(tree_, proc_pass_2);
|
||
|
||
// write functions
|
||
// need to declare CallArgs deps first
|
||
// only those, as the others are already all declared by this stage
|
||
if (auto fd = make_dep_declare(functions_deps, true); fd.size())
|
||
functions << fd << std::endl;
|
||
functions << functions_decl.str();
|
||
|
||
// generate callargs
|
||
if (call_args.tellp()) {
|
||
File ca(ns, h_call_args);
|
||
NamespaceGuard nsg_decl(ca);
|
||
nsg_decl.push(GI_NS_ARGS, false);
|
||
ca << call_args.str();
|
||
} else {
|
||
h_call_args = nullptr;
|
||
}
|
||
|
||
auto add_stub_include = [this](const std::string &suffix) {
|
||
auto fpath = (fs::path(tolower(ns)) / (tolower(ns) + suffix)).native();
|
||
return make_conditional_include(fpath, false);
|
||
};
|
||
|
||
auto add_stub_define = [this](bool impl, bool begin) {
|
||
auto nsu = toupper(ns);
|
||
auto infix = impl ? "IMPL_" : "";
|
||
auto suffix = begin ? "BEGIN" : "END";
|
||
auto macro = fmt::format("GI_INCLUDE_{}{}_{}", infix, nsu, suffix);
|
||
// avoid deprecated warning floods
|
||
auto guard = begin ? GI_DISABLE_DEPRECATED_WARN_BEGIN
|
||
: GI_DISABLE_DEPRECATED_WARN_END;
|
||
// also allow for custom/override tweak
|
||
return fmt::format("{}\n\n#ifdef {}\n{}\n#endif", guard, macro, macro);
|
||
};
|
||
|
||
auto h_ns = tolower(ns) + ".hpp";
|
||
auto h_ns_inc = tolower(ns) + "_inc.hpp";
|
||
auto h_ns_impl = tolower(ns) + "_impl.hpp";
|
||
|
||
// generate overall includes
|
||
File nsh_inc(ns, h_ns_inc, false);
|
||
// enable dl load coding
|
||
if (h_libs && *h_libs) {
|
||
nsh_inc << "#ifndef GI_DL" << std::endl;
|
||
nsh_inc << "#define GI_DL 1" << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
}
|
||
if (ctx.options.expected) {
|
||
nsh_inc << "#ifndef GI_EXPECTED" << std::endl;
|
||
nsh_inc << "#define GI_EXPECTED 1" << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
}
|
||
if (ctx.options.const_method) {
|
||
nsh_inc << "#ifndef GI_CONST_METHOD" << std::endl;
|
||
nsh_inc << "#define GI_CONST_METHOD 1" << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
}
|
||
if (ctx.options.classimpl) {
|
||
nsh_inc << "#ifndef GI_CLASS_IMPL" << std::endl;
|
||
nsh_inc << "#define GI_CLASS_IMPL 1" << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
}
|
||
if (ctx.options.call_args >= 0) {
|
||
nsh_inc << "#ifndef GI_CALL_ARGS" << std::endl;
|
||
nsh_inc << "#define GI_CALL_ARGS " << ctx.options.call_args << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
}
|
||
if (ctx.options.basic_collection) {
|
||
nsh_inc << "#ifndef GI_BASIC_COLLECTION" << std::endl;
|
||
nsh_inc << "#define GI_BASIC_COLLECTION 1" << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
}
|
||
nsh_inc << std::endl;
|
||
nsh_inc << make_include("gi/gi_inc.hpp", false) << std::endl;
|
||
nsh_inc << std::endl;
|
||
// preserve some behaviour for standard non-module code
|
||
nsh_inc << "#ifndef GI_MODULE_IN_INTERFACE" << std::endl;
|
||
nsh_inc << make_include("gi/gi.hpp", false) << std::endl;
|
||
nsh_inc << "#endif" << std::endl;
|
||
nsh_inc << std::endl;
|
||
// include gi deps; inc version
|
||
for (auto &&d : dep_headers) {
|
||
auto hd = boost::algorithm::replace_last_copy(d, ".hpp", "_inc.hpp");
|
||
nsh_inc << make_include(hd, false) << std::endl;
|
||
}
|
||
nsh_inc << std::endl;
|
||
// package includes
|
||
// in some cases, these are totally missing
|
||
// so the headers will have to be supplied by extra header override
|
||
// repo supplied
|
||
nsh_inc << add_stub_include("_setup_pre_def.hpp") << std::endl;
|
||
// user supplied
|
||
nsh_inc << add_stub_include("_setup_pre.hpp") << std::endl;
|
||
// include the above before the includes
|
||
// (so as to allow tweaking some defines in the overrides)
|
||
auto node = root_.get_child(EL_REPOSITORY);
|
||
auto p = node.equal_range(EL_CINCLUDE);
|
||
for (auto &q = p.first; q != p.second; ++q) {
|
||
// in some buggy cases, additional headers may have a full path
|
||
// let's only keep the last part
|
||
auto name = get_name(q->second);
|
||
if (name.size() && name[0] == '/') {
|
||
auto pos = name.rfind('/');
|
||
pos = (pos != name.npos && pos) ? name.rfind('/', pos - 1) : pos;
|
||
if (pos != name.npos)
|
||
name = name.substr(pos + 1);
|
||
}
|
||
nsh_inc << make_include(name, false) << std::endl;
|
||
}
|
||
// we may also need to tweak or fix things after usual includes
|
||
// repo supplied
|
||
nsh_inc << add_stub_include("_setup_post_def.hpp") << std::endl;
|
||
// user supplied
|
||
nsh_inc << add_stub_include("_setup_post.hpp") << std::endl;
|
||
nsh_inc << std::endl;
|
||
|
||
File nsh(ns, h_ns, false);
|
||
// include above part
|
||
nsh << make_include(h_ns_inc, true) << std::endl;
|
||
nsh << std::endl;
|
||
// gi deps; full version
|
||
nsh << "#ifndef GI_MODULE_NO_REC_INC" << std::endl;
|
||
for (auto &&d : dep_headers)
|
||
nsh << make_include(d, false) << std::endl;
|
||
nsh << "#endif" << std::endl;
|
||
// allow to tweak things (e.g. template specialization)
|
||
// prior to generated code declaration/definition
|
||
// repo supplied
|
||
nsh << add_stub_include("_extra_pre_def.hpp") << std::endl;
|
||
// user supplied
|
||
nsh << add_stub_include("_extra_pre.hpp") << std::endl;
|
||
nsh << std::endl;
|
||
|
||
// guard begin
|
||
nsh << add_stub_define(false, true) << std::endl;
|
||
nsh << std::endl;
|
||
|
||
// various basic declaration parts
|
||
for (auto &h : {h_types, h_enums, h_flags, h_constants, h_callbacks})
|
||
nsh << make_include(h, true) << std::endl;
|
||
nsh << std::endl;
|
||
|
||
// declarations
|
||
for (auto &h : includes)
|
||
nsh << make_include(h.first, true) << std::endl;
|
||
nsh << std::endl;
|
||
// global functions when we have seen all else
|
||
nsh << make_include(h_functions, true) << std::endl;
|
||
// all collected CallArgs
|
||
if (h_call_args)
|
||
nsh << make_include(h_call_args, true) << std::endl;
|
||
nsh << std::endl;
|
||
// allow for override/supplements
|
||
// repo supplied
|
||
nsh << add_stub_include("_extra_def.hpp") << std::endl;
|
||
// user supplied
|
||
nsh << add_stub_include("_extra.hpp") << std::endl;
|
||
// guard end
|
||
nsh << add_stub_define(false, false) << std::endl;
|
||
nsh << std::endl;
|
||
// include implementation header in inline case
|
||
nsh << "#if defined(GI_INLINE) || defined(GI_INCLUDE_IMPL)" << std::endl;
|
||
nsh << make_include(h_ns_impl, true) << std::endl;
|
||
nsh << "#endif" << std::endl;
|
||
nsh << std::endl;
|
||
|
||
File nsh_impl(ns, h_ns_impl, false);
|
||
// include declaration
|
||
nsh_impl << make_include(h_ns, true) << std::endl;
|
||
nsh_impl << std::endl;
|
||
// guard begin
|
||
nsh_impl << add_stub_define(true, true) << std::endl;
|
||
nsh_impl << std::endl;
|
||
// lib helper for symbol load
|
||
nsh_impl << make_include(h_libs, true) << std::endl;
|
||
nsh_impl << std::endl;
|
||
// implementations
|
||
for (auto &h : includes)
|
||
nsh_impl << make_include(h.second, true) << std::endl;
|
||
nsh_impl << std::endl;
|
||
nsh_impl << make_include(h_callbacks_impl, true) << std::endl;
|
||
nsh_impl << make_include(h_functions_impl, true) << std::endl;
|
||
nsh_impl << std::endl;
|
||
// likewise for override/supplement implementation
|
||
// repo supplied
|
||
nsh_impl << add_stub_include("_extra_def_impl.hpp") << std::endl;
|
||
// user supplied
|
||
nsh_impl << add_stub_include("_extra_impl.hpp") << std::endl;
|
||
nsh_impl << std::endl;
|
||
// guard end
|
||
nsh_impl << add_stub_define(false, true) << std::endl;
|
||
nsh_impl << std::endl;
|
||
|
||
// a convenience cpp for non-inline
|
||
auto cpp_ns = tolower(ns) + ".cpp";
|
||
File cpp(ns, cpp_ns, false, false);
|
||
cpp << make_include(h_ns_impl, true) << std::endl;
|
||
|
||
// NOTE it is not possible to provide (core) gi as a module,
|
||
// as it also provides a set of macros, and contains some forward
|
||
// declarations (ParamFlags, SignalFlags) which are only fully defined
|
||
// by generated code
|
||
// however, symbol/definitions are attached to module, so a later one
|
||
// can not define another one forward declared by another module
|
||
// in essence; modules break forward declarations
|
||
// so, 2 variants are created;
|
||
// + a separate one per namespace, but the "lowest" one is gobject,
|
||
// which then also includes glib and gi
|
||
// + a recursive one, a module that combines all dependency ns,
|
||
// so a large object and precompiled result
|
||
auto nsl = tolower(ns);
|
||
std::string glib_ns = "glib";
|
||
std::string gobject_ns = "gobject";
|
||
if (nsl != glib_ns) {
|
||
// lowest gobject is actually recursive
|
||
std::string modprefix{"gi.repo."};
|
||
bool is_gobject = nsl == gobject_ns;
|
||
if (!is_gobject) {
|
||
// module form; separate
|
||
auto cppm_ns = cpp_ns + "m";
|
||
File cppm(ns, cppm_ns, false, false);
|
||
auto module_templ = R"|(
|
||
module;
|
||
#define GI_INLINE 1
|
||
#define GI_MODULE_IN_INTERFACE 1
|
||
{0}
|
||
export module {1};
|
||
{2}
|
||
GI_MODULE_BEGIN
|
||
#define GI_MODULE_NO_REC_INC 1
|
||
export {{
|
||
{3}
|
||
}}
|
||
GI_MODULE_END
|
||
)|";
|
||
auto modname = modprefix + nsl;
|
||
std::ostringstream moddeps;
|
||
bool got_gobject = false;
|
||
for (auto &&d : dep_headers) {
|
||
std::string path = fs::path(d).filename();
|
||
boost::algorithm::erase_all(path, ".hpp");
|
||
// sigh, glib/gobject collapsed along with core
|
||
// replace references in non-duplicate way
|
||
if (path == glib_ns)
|
||
path = gobject_ns;
|
||
if (path == gobject_ns) {
|
||
if (got_gobject) {
|
||
continue;
|
||
} else {
|
||
got_gobject = true;
|
||
}
|
||
}
|
||
moddeps << "import " << modprefix << path << ';' << std::endl;
|
||
}
|
||
cppm << fmt::format(module_templ, make_include(h_ns_inc, true), modname,
|
||
moddeps.str(), make_include(h_ns, true));
|
||
}
|
||
|
||
{ // module form; recursive
|
||
auto cppm_ns = (is_gobject ? cpp_ns : nsl + "_rec.cpp") + "m";
|
||
File cppm(ns, cppm_ns, false, false);
|
||
auto module_templ = R"|(
|
||
module;
|
||
#define GI_INLINE 1
|
||
#define GI_MODULE_IN_INTERFACE 1
|
||
{0}
|
||
export module {1};
|
||
GI_MODULE_BEGIN
|
||
#include "gi/gi.hpp"
|
||
export {{
|
||
{2}
|
||
}}
|
||
GI_MODULE_END
|
||
)|";
|
||
auto modname = modprefix + nsl;
|
||
if (!is_gobject)
|
||
modname += ".rec";
|
||
cppm << fmt::format(module_templ, make_include(h_ns_inc, true), modname,
|
||
make_include(h_ns, true));
|
||
}
|
||
}
|
||
|
||
// optional build tool convenience
|
||
// generate refering files in rootdir
|
||
if (ctx.options.output_top) {
|
||
File top_cpp(ns, cpp_ns, false, false, false);
|
||
top_cpp << make_include(nsh_impl.get_rel_path(), true) << std::endl;
|
||
|
||
File top_hpp(ns, h_ns, false, false, false);
|
||
top_hpp << make_include(nsh.get_rel_path(), true) << std::endl;
|
||
}
|
||
|
||
return nsh.get_rel_path();
|
||
}
|
||
};
|
||
|
||
} // namespace
|
||
|
||
std::shared_ptr<NamespaceGenerator>
|
||
NamespaceGenerator::new_(GeneratorContext &ctx, const std::string &filename)
|
||
{
|
||
return std::make_shared<NamespaceGeneratorImpl>(ctx, filename);
|
||
}
|