Skip to content

Commit

Permalink
GI: reduce code duplication and remove dead code
Browse files Browse the repository at this point in the history
  • Loading branch information
jwahlstrand committed Apr 7, 2024
1 parent 9af51b4 commit e59e627
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 101 deletions.
20 changes: 8 additions & 12 deletions GI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,24 @@ Julia bindings using libgobject-introspection.

This builds on https://github.com/bfredl/GI.jl

The goal is to output code that simplifies the creation of Julia packages that wrap GObject-based libraries.
It outputs constants (including enums and flags types), struct definitions, and function definitions that wrap `ccall`'s of GObject based libraries.
In function definitions, it uses [annotations](https://gi.readthedocs.io/en/latest/annotations/giannotations.html) to determine whether return values
should be freed, whether pointer arguments can be optionally NULL, whether list
outputs are NULL-terminated, which argument corresponds to the length of array
inputs, which arguments are outputs and which are inputs, and more.
## Scope

This package outputs code that simplifies the creation of Julia packages that wrap GObject-based libraries.
It outputs constants (including enums and flags types), struct definitions, callback definitions, and function definitions that wrap `ccall`'s of GObject based libraries.
In function definitions, it uses [annotations](https://gi.readthedocs.io/en/latest/annotations/giannotations.html) to determine whether return values should be freed, whether pointer arguments can be optionally NULL, whether list outputs are NULL-terminated, which argument corresponds to the length of array inputs, which arguments are outputs and which are inputs, and more.
The primary advantage over manually writing `ccall`'s (as is done in Gtk.jl) is that it can rapidly cover an entire library, saving a lot of tedious work.
As new functionality is added to libraries, you just have to run GI.jl again and new code is generated.
Disadvantages include: the current implementation only extracts GI information on Linux, leading to potential bugs on other platforms, and annotations are inaccurate in some libraries.
The advantage over using Clang to generate wrappers is the fact that annotations provide important information, like whether outputs are "owned" or not, which arguments are closures or array lengths, and so on.
Disadvantages: the current implementation only extracts GI information on Linux, leading to potential bugs on other platforms.

This package is currently unregistered, and it only works on Linux because it uses [gobject_introspection_jll](https://github.com/JuliaPackaging/Yggdrasil/tree/master/G/gobject_introspection), which is currently only available for Linux. However, most generated code works on other platforms.
This package is unregistered and it only works on Linux because it uses [gobject_introspection_jll](https://github.com/JuliaPackaging/Yggdrasil/tree/master/G/gobject_introspection), which is currently only available for Linux. However, most generated code works on other platforms.

## Status

Most of libgirepository is wrapped.
This allows information like lists of structs, methods, and functions to be extracted, as well as argument types, struct fields, etc.
Using this information, GI.jl produces Julia code.

Parts that are still very rough:

* Anything to do with callbacks and signals - there is probably a smart way to use the information from introspection for these, but it hasn't been a priority yet.

## Generated code

Code generated by GI.jl currently requires [Gtk4.jl](https://github.com/JuliaGtk/Gtk4.jl).
Expand Down
108 changes: 19 additions & 89 deletions GI/src/giimport.jl
Original file line number Diff line number Diff line change
@@ -1,21 +1,6 @@
# use libgirepository to produce Julia declarations and methods

const_expr(name,val) = :(const $(Symbol(name)) = $(val))

# export enum using a baremodule, as is done in Gtk.jl
function enum_decl(enum)
enumname=get_name(enum)
vals = get_enum_values(enum)
body = Expr(:block)
for (name,val) in vals
if match(r"^[a-zA-Z_]",string(name)) === nothing
name = Symbol("_$name")
end
push!(body.args, :(const $(symuppercase(name)) = $val) )
end
Expr(:toplevel, Expr(:module, false, Symbol(enumname), body))
end

enum_fullname(enumname,name) = Symbol(enumname,"_",symuppercase(name))
enum_name(enum) = Symbol(string(get_namespace(enum),get_name(enum)))

Expand All @@ -28,23 +13,26 @@ end

find_symbol(id) = Base.Fix2(find_symbol, id)

function typeinit_def(info)
name=get_name(info)
ti = get_type_init(info)
if ti === :nothing
return nothing
end
type_init = String(ti)
function typeinit_def(info, gstructname = get_name(info))
type_init = String(get_type_init(info))
libs=get_shlibs(GINamespace(get_namespace(info)))
lib=libs[findfirst(find_symbol(type_init),libs)]
slib=symbol_from_lib(lib)
gtypeinit = quote
GLib.g_type(::Type{T}) where {T <: $name} =
ccall(($type_init, $slib), GType, ())
quote
GLib.g_type(::Type{T}) where {T <: $gstructname} =
ccall(($type_init, $(symbol_from_lib(lib))), GType, ())
end
end

function append_type_init(body, enum, incl_typeinit)
bloc = Expr(:block, unblock(body))
# if this enum has a GType we define GLib.g_type() to support storing this in GValue
if incl_typeinit && get_type_init(enum) !== :nothing
push!(bloc.args,unblock(typeinit_def(enum)))
end
unblock(bloc)
end

# export as a Julia enum type
# export as a Cenum type
function decl(enum::GIEnumInfo, incl_typeinit=true)
enumname=get_name(enum)
vals = get_enum_values(enum)
Expand All @@ -55,13 +43,7 @@ function decl(enum::GIEnumInfo, incl_typeinit=true)
fullname=enum_fullname(enumname,name)
push!(body.args, :($fullname = $val) )
end
bloc = Expr(:block, unblock(body))
# if this enum has a GType we define GLib.g_type() to support storing this in GValue
if incl_typeinit
gtypeinit = typeinit_def(enum)
gtypeinit !== nothing && push!(bloc.args,unblock(gtypeinit))
end
unblock(bloc)
append_type_init(body, enum, incl_typeinit)
end

# use BitFlags.jl
Expand All @@ -83,12 +65,7 @@ function decl(enum::GIFlagsInfo, incl_typeinit=true)
fullname=enum_fullname(enumname,"NONE")
push!(body.args, :($fullname = 0))
end
bloc = Expr(:block, body)
if incl_typeinit
gtypeinit = typeinit_def(enum)
push!(bloc.args,unblock(gtypeinit))
end
unblock(bloc)
append_type_init(body, enum, incl_typeinit)
end

## Struct output
Expand All @@ -111,13 +88,8 @@ function decl(structinfo::GIStructInfo,force_opaque=false)
isboxed = GLib.g_isa(gtype,GLib.g_type(GBoxed))
exprs=Expr[]
if isboxed
type_init = String(get_type_init(structinfo))
libs=get_shlibs(GINamespace(get_namespace(structinfo)))
lib=libs[findfirst(find_symbol(type_init),libs)]
slib=symbol_from_lib(lib)
fin = quote
GLib.g_type(::Type{T}) where {T <: $gstructname} =
ccall(($type_init, $slib), GType, ())
$(unblock(typeinit_def(structinfo,gstructname)))
function $gstructname(ref::Ptr{T}, own::Bool = false) where {T <: GBoxed}
#println("constructing ",$(QuoteNode(gstructname)), " ",own)
x = new(ref)
Expand Down Expand Up @@ -184,42 +156,6 @@ end

## GObject/GTypeInstance output

# extract properties for a particular GObject type
# Most property info is available in real time from libgobject, so it turns out there is no
# point in using this beyond gaining type stability for properties, and it's not clear that
# is worth the trouble. There has been discussion of connecting properties to getter/setter
# functions in the introspection data. So it might be worth revisiting this someday.

function prop_dict(info)
properties=get_properties(info)
d=Dict{Symbol,Tuple{Any,Int32,Int32}}()
for p in properties
# whether the property is readable, writable, other stuff
flags=get_flags(p)

# in practice it looks like this is never set to anything but TRANSFER_NONE, so we could omit it
tran=get_ownership_transfer(p)

typ=get_type(p)
btyp=get_base_type(typ)
ptyp=extract_type(typ,btyp)
name=Symbol(replace(String(get_name(p)),"-"=>"_"))
d[name]=(ptyp.jstype,tran,flags)
end
d
end

# extract properties for a particular GObject type and all its parents
function prop_dict_incl_parents(objectinfo::GIObjectInfo)
d=prop_dict(objectinfo)
parentinfo=get_parent(objectinfo)
if parentinfo!==nothing
return merge(prop_dict_incl_parents(parentinfo),d)
else
return d
end
end

function signal_dict(info)
signals=get_signals(info)
d=Dict{Symbol,Tuple{Any,Any}}()
Expand Down Expand Up @@ -276,11 +212,6 @@ function gobject_decl(objectinfo)
println("get_g_type returns void -- not in library? : ", get_name(objectinfo))
end

type_init = String(get_type_init(objectinfo))
libs=get_shlibs(GINamespace(get_namespace(objectinfo)))
lib=libs[findfirst(find_symbol(type_init),libs)]
slib=symbol_from_lib(lib)

exprs=Expr[]
decl=quote
abstract type $oname <: $pname end
Expand All @@ -295,8 +226,7 @@ function gobject_decl(objectinfo)
end
end
gtype_wrapper_cache[$(QuoteNode(oname))] = $leafname
GLib.g_type(::Type{T}) where {T <: $oname} =
ccall(($type_init, $slib), GType, ())
$(unblock(typeinit_def(objectinfo, oname)))
end
push!(exprs, decl)
# if there are signals, add "signal_return_type" method, and "signal_arg_types" method
Expand Down

0 comments on commit e59e627

Please sign in to comment.