-
Notifications
You must be signed in to change notification settings - Fork 13
/
generator.jl
189 lines (158 loc) · 5.62 KB
/
generator.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#=
Use Clang.jl to wrap the GDAL C API.
Several custom transformations are applied that should make using this package more convenient.
- docstrings are added, created from GDAL Doxygen XML output
- function names are lowercased following Julia conventions
- functions that return a Cstring are wrapped in unsafe_string to return a String
- functions that return a Ptr{Cstring} are wrapped in unsafe_loadstringlist to return a Vector{String}
- functions that return a Ptr{Cvoid} are wrapped in failsafe to catch errors
=#
using Clang
using Clang.Generators
using MacroTools
using Logging
using EzXML
using GDAL_jll
using JuliaFormatter
const output_dir = joinpath(@__DIR__, "..", "src")
const xmlpath = joinpath(@__DIR__, "doxygen.xml")
# set up a global logger to log.txt to store the large amount of logging
loghandle = open(joinpath(@__DIR__, "log.txt"), "w")
logger = SimpleLogger(loghandle)
global_logger(logger)
# several functions for building docstrings
include(joinpath(@__DIR__, "doc.jl"))
includedir = joinpath(GDAL_jll.artifact_dir, "include")
headerfiles =
joinpath.(
includedir,
[
"cpl_conv.h",
"cpl_error.h",
"cpl_minixml.h",
"cpl_port.h",
"cpl_progress.h",
"cpl_virtualmem.h",
"cpl_vsi.h",
"gdal.h",
"gdal_alg.h",
"gdalwarper.h",
"gdal_vrt.h",
"gdal_utils.h",
"ogr_api.h",
"ogr_core.h",
"ogr_srs_api.h",
],
)
for headerfile in headerfiles
if !isfile(headerfile)
error("Header file missing `($headerfile)")
end
end
# functions that return a Cstring, of which we know we must free them
# because for instance the documentation says: "Free with CPLFree() or VSIFree()"
# use the renamed (lowercased) function names here
# if it is not in this list, it will not be freed, add function names to make it more complete
const string_free_me = Symbol[:gdalinfo]
"""
Custom rewriter for Clang.jl's C wrapper
Gets called with all expressions in a header file, or all expressiong in a common file.
If available, it adds docstrings before every expression, such that Clang.jl prints them
on top of the expression. The expressions themselves get sent to `rewriter(::Expr)`` for
further treatment.
"""
function rewriter(xs::Vector)
rewritten = Any[]
for x in xs
# Clang.jl inserts strings like "# Skipping MacroDefinition: X"
# keep these to get a sense of what we are missing
if x isa String
push!(rewritten, x)
continue
end
@assert x isa Expr
name = cname(x)
node = findnode(name, doc)
docstr = node === nothing ? "" : build_docstring(node)
isempty(docstr) || push!(rewritten, addquotes(docstr))
x2 = rewriter(x)
push!(rewritten, x2)
end
rewritten
end
"Rewrite expressions in the ways listed at the top of this file."
function rewriter(x::Expr)
if @capture(x, function f_(fargs__)
ccall(fname_, rettype_, argtypes_, argvalues__)
end)
# it is a function wrapper around a ccall
# lowercase the function name
f2 = Symbol(lowercase(String(f)))
# replace Ptr{Cvoid} with Any for callbacks, so Functions are converted by Julia
# https://julialang.org/blog/2013/05/callback/#passing_closures_via_pass-through_pointers
# The callback always follows the GDALProgressFunc type
i = findfirst(==(:GDALProgressFunc), argtypes.args)
if !isnothing(i) && argtypes.args[i+1] == :(Ptr{Cvoid})
argtypes.args[i+1] = :Any
end
# bind the ccall such that we can easily wrap it
cc = :(ccall($fname, $rettype, $argtypes, $(argvalues...)))
cc2 = if occursin(r"^cpl.*error", String(f2))
# We will take care of GDAL error states and memory freeing by
# calling `aftercare`, except for `cpl*error*` functions, as these
# will be called by `aftercare` itself, so we prevent a loop.
if rettype == :Cstring
:(unsafe_string($cc))
else
cc
end
elseif rettype == :Cstring
# boolean to let aftercare know if it should call vsifree
free = f2 in string_free_me
:(aftercare($cc, $free))
else
:(aftercare($cc))
end
# stitch the modified function expression back together
:(function $f2($(fargs...))
$cc2
end) |> prettify
else
# do not modify expressions that are no ccall function wrappers
x
end
end
# parse GDAL's Doxygen XML file
const doc = readxml(xmlpath)
# custom function that prevents overwriting GDAL.jl by mapping gdal.h to gdal_h.jl
function header_outputfile(h)
stem = splitext(basename(h))[1]
if stem == "gdal"
joinpath(output_dir, "gdal_h.jl")
else
joinpath(output_dir, stem * ".jl")
end
end
function rewriter(dag::Clang.ExprDAG)
for node in get_nodes(dag)
# Macros are not generated, so no need to skip
map!(rewriter, node.exprs, node.exprs)
end
end
# do not wrap, handled in prologue.jl
@add_def stat
@add_def _stat64
@add_def time_t
options = load_options(joinpath(@__DIR__, "generator.toml"))
# add compiler flags, e.g. "-DXXXXXXXXX"
args = get_default_args()
push!(args, "-I$includedir")
# create context
ctx = create_context(headerfiles, args, options)
# run generator
build!(ctx, BUILDSTAGE_NO_PRINTING)
rewriter(ctx.dag)
build!(ctx, BUILDSTAGE_PRINTING_ONLY)
add_doc(joinpath(@__DIR__, "..", "src", "libgdal.jl"))
format(joinpath(@__DIR__, ".."))
close(loghandle)