#include "genns.hpp" #include "fs.hpp" #include "function.hpp" #include "genbase.hpp" #include #include #include #include #include #include #include #include namespace { class File : public std::ofstream { std::string ns; std::string fname; bool preamble; bool guard; 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) { fs::path p(root); 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) : std::ofstream(prepdirs(_ns, _fname)), ns(_ns), fname(_fname), preamble(_need_ns), guard(_need_guard), nsg(*this) { write_pre(); } std::string get_rel_path() const { return (fs::path(tolower(ns)) / tolower(fname)).native(); } 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 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 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; std::set 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 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( "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, std::set &deps) const { auto &node = entry.second; ArgInfo tinfo; auto name = get_name(node); auto read = get_attribute(node, AT_READABLE, 1); auto write = get_attribute(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, std::set &deps) const { auto &node = entry.second; auto name = get_name(node); auto readable = get_attribute(node, AT_READABLE, 1); auto writable = get_attribute(node, AT_WRITABLE, 1); auto priv = get_attribute(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 ¶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("static {} {} ({} {}) {{ 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("static 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, std::set &deps) const { return ::process_element_function( ctx, ns, entry, out, impl, klass, klasstype, deps, 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 std::set &deps) const { std::ostringstream oss; for (const auto &d : deps) { auto c = unqualify(d); if (!is_qualified(c)) oss << "class " << c << ";" << std::endl; } 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> VirtualMethods; void process_element_record_class(const pt::ptree::value_type &entry, const std::vector &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 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 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 constexpr static TypeInitData factory() {{ {3}using DefData = detail::DefinitionData; 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 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})) " "detail::method_wrapper::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 record_collect_interfaces( const pt::ptree::value_type &entry, const TypeInfo ¤t, const TypeInfo &parent, std::set &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 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 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 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 process_element_record( const pt::ptree::value_type &entry, bool onlyverify) 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; std::set deps; for (const auto &n : node) { auto el = n.first; int introspectable = get_attribute(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); } 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); 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) out_decl << make_dep_include(parent.girname); out_decl << std::endl; // implemented interfaces are also dependency std::vector 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(&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 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 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 _libs() {{ return {{{0}}}; }} }} // internal )|"; libs << fmt::format(libs_templ, boost::join(qshlibs, ",")); return h_libs; } public: std::string process_tree(const std::vector &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(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"; 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); 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); } }; 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 std::set 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> includes; std::set dummy; 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); includes.insert({std::get<0>(res), std::get<1>(res)}); } else if (el == EL_FUNCTION && visit_ok(n)) { process_element_function(n, functions, functions_impl, "", "", dummy); } else if ((el == EL_ENUM || el == EL_FLAGS) && visit_ok(n)) { process_element_enum(n, functions, &functions_impl); } }; process_entries(tree_, proc_pass_2); 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_impl = tolower(ns) + "_impl.hpp"; // generate overall includes File nsh(ns, h_ns, false); // enable dl load coding if (h_libs && *h_libs) { nsh << "#ifndef GI_DL" << std::endl; nsh << "#define GI_DL 1" << std::endl; nsh << "#endif" << std::endl; } if (ctx.options.expected) { nsh << "#ifndef GI_EXPECTED" << std::endl; nsh << "#define GI_EXPECTED 1" << std::endl; nsh << "#endif" << std::endl; } if (ctx.options.const_method) { nsh << "#ifndef GI_CONST_METHOD" << std::endl; nsh << "#define GI_CONST_METHOD 1" << std::endl; nsh << "#endif" << std::endl; } nsh << make_include("gi/gi.hpp", false) << std::endl; nsh << std::endl; // include gi deps for (auto &&d : dep_headers) nsh << make_include(d, false) << std::endl; nsh << 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 << add_stub_include("_setup_pre_def.hpp") << std::endl; // user supplied nsh << 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 << make_include(name, false) << std::endl; } // we may also need to tweak or fix things after usual includes // repo supplied nsh << add_stub_include("_setup_post_def.hpp") << std::endl; // user supplied nsh << add_stub_include("_setup_post.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; 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; return nsh.get_rel_path(); } }; } // namespace std::shared_ptr NamespaceGenerator::new_(GeneratorContext &ctx, const std::string &filename) { return std::make_shared(ctx, filename); }