util-lib.lua /size: 17 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
util-lib
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to luat-lib.mkiv
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
,
7
}
8 9
--[[ 10 11The problem with library bindings is manyfold. They are of course platform 12dependent and while a binary with its directly related libraries are often 13easy to maintain and load, additional libraries can each have their demands. 14 15One important aspect is that loading additional libraries from within the 16loaded one is also operating system dependent. There can be shared libraries 17elsewhere on the system and as there can be multiple libraries with the same 18name but different usage and versioning there can be clashes. So there has to 19be some logic in where to look for these sublibraries. 20 21We found out that for instance on windows libraries are by default sought on 22the parents path and then on the binary paths and these of course can be in 23an out of our control, thereby enlarging the changes on a clash. A rather 24safe solution for that to load the library on the path where it sits. 25 26Another aspect is initialization. When you ask for a library t.e.x it will 27try to initialize luaopen_t_e_x no matter if such an inializer is present. 28However, because loading is configurable and in the case of luatex is already 29partly under out control, this is easy to deal with. We only have to make 30sure that we inform the loader that the library has been loaded so that 31it won't load it twice. 32 33In swiglib we have chosen for a clear organization and although one can use 34variants normally in the tex directory structure predictability is more or 35less the standard. For instance: 36 37.../tex/texmf-mswin/bin/lib/luatex/lua/swiglib/mysql/core.dll 38.../tex/texmf-mswin/bin/lib/luajittex/lua/swiglib/mysql/core.dll 39.../tex/texmf-mswin/bin/lib/luatex/context/lua/swiglib/mysql/core.dll 40.../tex/texmf-mswin/bin/lib/swiglib/lua/mysql/core.dll 41.../tex/texmf-mswin/bin/lib/swiglib/lua/mysql/5.6/core.dll 42 43The lookups are determined via an entry in texmfcnf.lua: 44 45CLUAINPUTS = ".;$SELFAUTOLOC/lib/{$engine,luatex}/lua//", 46 47A request for t.e.x is converted to t/e/x.dll or t/e/x.so depending on the 48platform. Then we use the regular finder to locate the file in the tex 49directory structure. Once located we goto the path where it sits, load the 50file and return to the original path. We register as t.e.x in order to 51prevent reloading and also because the base name is seldom unique. 52 53The main function is a big one and evolved out of experiments that Luigi 54Scarso and I conducted when playing with variants of SwigLib. The function 55locates the library using the context mkiv resolver that operates on the 56tds tree and if that doesn't work out well, the normal clib path is used. 57 58The lookups is somewhat clever in the sense that it can deal with (optional) 59versions and can fall back on non versioned alternatives if needed, either 60or not using a wildcard lookup. 61 62This code is experimental and by providing a special abstract loader (called 63swiglib) we can start using the libraries. 64 65A complication is that we might end up with a luajittex path matching before a 66luatex path due to the path spec. One solution is to first check with the engine 67prefixed. This could be prevented by a more strict lib pattern but that is not 68always under our control. So, we first check for paths with engine in their name 69and then without. 70 71]]
--
72 73
local
type
=
type
74
local
next
=
next
75
local
pcall
=
pcall
76
local
gsub
=
string
.
gsub
77
local
find
=
string
.
find
78
local
sort
=
table
.
sort
79
local
pathpart
=
file
.
pathpart
80
local
nameonly
=
file
.
nameonly
81
local
joinfile
=
file
.
join
82
local
removesuffix
=
file
.
removesuffix
83
local
addsuffix
=
file
.
addsuffix
84
local
findfile
=
resolvers
.
findfile
85
local
findfiles
=
resolvers
.
findfiles
86
local
expandpaths
=
resolvers
.
expandedpathlistfromvariable
87
local
qualifiedpath
=
file
.
is_qualified_path
88
local
isfile
=
lfs
.
isfile
89 90
local
done
=
false
91 92
-- We can check if there are more that one component, and if not, we can
93
-- append 'core'.
94 95
local
function
locate
(
required
,
version
,
trace
,
report
,
action
)
96
if
type
(
required
)
~
=
"
string
"
then
97
report
(
"
provide a proper library name
"
)
98
return
99
end
100
if
trace
then
101
report
(
"
requiring library %a with version %a
"
,
required
,
version
or
"
any
"
)
102
end
103
local
found_library
=
nil
104
local
required_full
=
gsub
(
required
,
"
%.
"
,
"
/
"
)
-- package.helpers.lualibfile
105
local
required_path
=
pathpart
(
required_full
)
106
local
required_base
=
nameonly
(
required_full
)
107
if
qualifiedpath
(
required
)
then
108
-- also check with suffix
109
if
isfile
(
addsuffix
(
required
,
os
.
libsuffix
)
)
then
110
if
trace
then
111
report
(
"
qualified name %a found
"
,
required
)
112
end
113
found_library
=
required
114
else
115
if
trace
then
116
report
(
"
qualified name %a not found
"
,
required
)
117
end
118
end
119
else
120
-- initialize a few variables
121
local
required_name
=
required_base
.
.
"
.
"
.
.
os
.
libsuffix
122
local
version
=
type
(
version
)
=
=
"
string
"
and
version
~
=
"
"
and
version
or
false
123
-- local engine = "luatex" -- environment.ownmain or false
124
local
engine
=
environment
.
ownmain
or
false
125
--
126
if
trace
and
not
done
then
127
local
list
=
expandpaths
(
"
lib
"
)
-- fresh, no reuse
128
for
i
=
1
,
#
list
do
129
report
(
"
tds path %i: %s
"
,
i
,
list
[
i
]
)
130
end
131
end
132
-- helpers
133
local
function
found
(
locate
,
asked_library
,
how
,
...
)
134
if
trace
then
135
report
(
"
checking %s: %a
"
,
how
,
asked_library
)
136
end
137
return
locate
(
asked_library
,
...
)
138
end
139
local
function
check
(
locate
,
...
)
140
local
found
=
nil
141
if
version
then
142
local
asked_library
=
joinfile
(
required_path
,
version
,
required_name
)
143
if
trace
then
144
report
(
"
checking %s: %a
"
,
"
with version
"
,
asked_library
)
145
end
146
found
=
locate
(
asked_library
,
...
)
147
end
148
if
not
found
or
found
=
=
"
"
then
149
local
asked_library
=
joinfile
(
required_path
,
required_name
)
150
if
trace
then
151
report
(
"
checking %s: %a
"
,
"
with version
"
,
asked_library
)
152
end
153
found
=
locate
(
asked_library
,
...
)
154
end
155
return
found
and
found
~
=
"
"
and
found
or
false
156
end
157
-- Alternatively we could first collect the locations and then do the two attempts
158
-- on this list but in practice this is not more efficient as we might have a fast
159
-- match anyway.
160
local
function
attempt
(
checkpattern
)
161
-- check cnf spec using name and version
162
if
trace
then
163
report
(
"
checking tds lib paths strictly
"
)
164
end
165
local
found
=
findfile
and
check
(
findfile
,
"
lib
"
)
166
if
found
and
(
not
checkpattern
or
find
(
found
,
checkpattern
)
)
then
167
return
found
168
end
169
-- check cnf spec using wildcard
170
if
trace
then
171
report
(
"
checking tds lib paths with wildcard
"
)
172
end
173
local
asked_library
=
joinfile
(
required_path
,
"
.*
"
,
required_name
)
174
if
trace
then
175
report
(
"
checking %s: %a
"
,
"
latest version
"
,
asked_library
)
176
end
177
local
list
=
findfiles
(
asked_library
,
"
lib
"
,
true
)
178
if
list
and
#
list
>
0
then
179
sort
(
list
)
180
local
found
=
list
[
#
list
]
181
if
found
and
(
not
checkpattern
or
find
(
found
,
checkpattern
)
)
then
182
return
found
183
end
184
end
185
-- Check lib paths using name and version.
186
if
trace
then
187
report
(
"
checking lib paths
"
)
188
end
189
package
.
extralibpath
(
environment
.
ownpath
)
190
local
paths
=
package
.
libpaths
(
)
191
local
pattern
=
"
/[^/]+%.
"
.
.
os
.
libsuffix
.
.
"
$
"
192
for
i
=
1
,
#
paths
do
193
required_path
=
gsub
(
paths
[
i
]
,
pattern
,
"
"
)
194
local
found
=
check
(
lfs
.
isfound
)
195
if
type
(
found
)
=
=
"
string
"
and
(
not
checkpattern
or
find
(
found
,
checkpattern
)
)
then
196
return
found
197
end
198
end
199
return
false
200
end
201
if
engine
then
202
if
trace
then
203
report
(
"
attemp 1, engine %a
"
,
engine
)
204
end
205
found_library
=
attempt
(
"
/
"
.
.
engine
.
.
"
/
"
)
206
if
not
found_library
then
207
if
trace
then
208
report
(
"
attemp 2, no engine
"
,
asked_library
)
209
end
210
found_library
=
attempt
(
)
211
end
212
else
213
found_library
=
attempt
(
)
214
end
215
end
216
-- load and initialize when found
217
if
not
found_library
then
218
if
trace
then
219
report
(
"
not found: %a
"
,
required
)
220
end
221
library
=
false
222
else
223
if
trace
then
224
report
(
"
found: %a
"
,
found_library
)
225
end
226
local
result
,
message
=
action
(
found_library
,
required_base
)
227
if
result
then
228
library
=
result
229
else
230
library
=
false
231
report
(
"
load error: message %a, library %a
"
,
tostring
(
message
or
"
unknown
"
)
,
found_library
or
"
no library
"
)
232
end
233
end
234
if
trace
then
235
if
not
library
then
236
report
(
"
unknown library: %a
"
,
required
)
237
else
238
report
(
"
stored library: %a
"
,
required
)
239
end
240
end
241
return
library
or
nil
242
end
243 244
resolvers
.
locatelib
=
locate
-- for now
245 246
do
247 248
local
report_swiglib
=
logs
.
reporter
(
"
swiglib
"
)
249
local
trace_swiglib
=
false
250
local
savedrequire
=
require
251
local
loadedlibs
=
{
}
252
local
loadlib
=
package
.
loadlib
253 254
local
pushdir
=
dir
.
push
255
local
popdir
=
dir
.
pop
256 257
trackers
.
register
(
"
resolvers.swiglib
"
,
function
(
v
)
trace_swiglib
=
v
end
)
258 259
function
requireswiglib
(
required
,
version
)
260
local
library
=
loadedlibs
[
library
]
261
if
library
=
=
nil
then
262
local
trace_swiglib
=
trace_swiglib
or
package
.
helpers
.
trace
263
library
=
locate
(
required
,
version
,
trace_swiglib
,
report_swiglib
,
function
(
name
,
base
)
264
pushdir
(
pathpart
(
name
)
)
265
local
opener
=
"
luaopen_
"
.
.
base
266
if
trace_swiglib
then
267
report_swiglib
(
"
opening: %a with %a
"
,
name
,
opener
)
268
end
269
local
library
,
message
=
loadlib
(
name
,
opener
)
270
local
libtype
=
type
(
library
)
271
if
libtype
=
=
"
function
"
then
272
library
=
library
(
)
273
else
274
report_swiglib
(
"
load error: %a returns %a, message %a, library %a
"
,
opener
,
libtype
,
(
string
.
gsub
(
message
or
"
no message
"
,
"
[%s]+$
"
,
"
"
)
)
,
found_library
or
"
no library
"
)
275
library
=
false
276
end
277
popdir
(
)
278
return
library
279
end
)
280
loadedlibs
[
required
]
=
library
or
false
281
end
282
return
library
283
end
284 285
--[[ 286 287For convenience we make the require loader function swiglib aware. Alternatively 288we could put the specific loader in the global namespace. 289 290]]
--
291 292
function
require
(
name
,
version
)
293
if
find
(
name
,
"
^swiglib%.
"
)
then
294
return
requireswiglib
(
name
,
version
)
295
else
296
return
savedrequire
(
name
)
297
end
298
end
299 300
--[[ 301 302At the cost of some overhead we provide a specific loader so that we can keep 303track of swiglib usage which is handy for development. In context this is the 304recommended loader. 305 306]]
--
307 308
local
swiglibs
=
{
}
309
local
initializer
=
"
core
"
310 311
function
swiglib
(
name
,
version
)
312
local
library
=
swiglibs
[
name
]
313
if
not
library
then
314
statistics
.
starttiming
(
swiglibs
)
315
if
trace_swiglib
then
316
report_swiglib
(
"
loading %a
"
,
name
)
317
end
318
if
not
find
(
name
,
"
%.
"
.
.
initializer
.
.
"
$
"
)
then
319
fullname
=
"
swiglib.
"
.
.
name
.
.
"
.
"
.
.
initializer
320
else
321
fullname
=
"
swiglib.
"
.
.
name
322
end
323
library
=
requireswiglib
(
fullname
,
version
)
324
swiglibs
[
name
]
=
library
325
statistics
.
stoptiming
(
swiglibs
)
326
end
327
return
library
328
end
329 330
statistics
.
register
(
"
used swiglibs
"
,
function
(
)
331
if
next
(
swiglibs
)
then
332
return
string
.
format
(
"
%s, initial load time %s seconds
"
,
table
.
concat
(
table
.
sortedkeys
(
swiglibs
)
,
"
"
)
,
statistics
.
elapsedtime
(
swiglibs
)
)
333
end
334
end
)
335 336
end
337 338
if
FFISUPPORTED
and
ffi
and
ffi
.
load
then
339 340
--[[ 341 342We use the same lookup logic for ffi loading. 343 344]]
--
345 346
local
report_ffilib
=
logs
.
reporter
(
"
ffilib
"
)
347
local
trace_ffilib
=
false
348
local
savedffiload
=
ffi
.
load
349 350
-- local pushlibpath = package.pushlibpath
351
-- local poplibpath = package.poplibpath
352 353
-- ffi.savedload = savedffiload
354 355
trackers
.
register
(
"
resolvers.ffilib
"
,
function
(
v
)
trace_ffilib
=
v
end
)
356 357
-- pushlibpath(pathpart(name))
358
-- local state, library = pcall(savedffiload,nameonly(name))
359
-- poplibpath()
360 361
local
loaded
=
{
}
362 363
local
function
locateindeed
(
name
)
364
name
=
removesuffix
(
name
)
365
local
l
=
loaded
[
name
]
366
if
l
=
=
nil
then
367
local
state
,
library
=
pcall
(
savedffiload
,
name
)
368
if
type
(
library
)
=
=
"
userdata
"
then
369
l
=
library
370
elseif
type
(
state
)
=
=
"
userdata
"
then
371
l
=
state
372
else
373
l
=
false
374
end
375
loaded
[
name
]
=
l
376
elseif
trace_ffilib
then
377
report_ffilib
(
"
reusing already loaded %a
"
,
name
)
378
end
379
return
l
380
end
381 382
local
function
getlist
(
required
)
383
local
list
=
directives
.
value
(
"
system.librarynames
"
)
384
if
type
(
list
)
=
=
"
table
"
then
385
list
=
list
[
required
]
386
if
type
(
list
)
=
=
"
table
"
then
387
if
trace
then
388
report
(
"
using lookup list for library %a: % | t
"
,
required
,
list
)
389
end
390
return
list
391
end
392
end
393
return
{
required
}
394
end
395 396
function
ffilib
(
name
,
version
)
397
name
=
removesuffix
(
name
)
398
local
l
=
loaded
[
name
]
399
if
l
~
=
nil
then
400
if
trace_ffilib
then
401
report_ffilib
(
"
reusing already loaded %a
"
,
name
)
402
end
403
return
l
404
end
405
local
list
=
getlist
(
name
)
406
if
version
=
=
"
system
"
then
407
for
i
=
1
,
#
list
do
408
local
library
=
locateindeed
(
list
[
i
]
)
409
if
type
(
library
)
=
=
"
userdata
"
then
410
return
library
411
end
412
end
413
else
414
for
i
=
1
,
#
list
do
415
local
library
=
locate
(
list
[
i
]
,
version
,
trace_ffilib
,
report_ffilib
,
locateindeed
)
416
if
type
(
library
)
=
=
"
userdata
"
then
417
return
library
418
end
419
end
420
end
421
end
422 423
function
ffi
.
load
(
name
)
424
local
list
=
getlist
(
name
)
425
for
i
=
1
,
#
list
do
426
local
library
=
ffilib
(
list
[
i
]
)
427
if
type
(
library
)
=
=
"
userdata
"
then
428
return
library
429
end
430
end
431
if
trace_ffilib
then
432
report_ffilib
(
"
trying to load %a using normal loader
"
,
name
)
433
end
434
-- so here we don't store
435
for
i
=
1
,
#
list
do
436
local
state
,
library
=
pcall
(
savedffiload
,
list
[
i
]
)
437
if
type
(
library
)
=
=
"
userdata
"
then
438
return
library
439
elseif
type
(
state
)
=
=
"
userdata
"
then
440
return
library
441
end
442
end
443
end
444 445
end
446 447
--[[ 448 449-- So, we now have: 450 451trackers.enable("resolvers.ffilib") 452trackers.enable("resolvers.swiglib") 453 454local gm = require("swiglib.graphicsmagick.core") 455local gm = swiglib("graphicsmagick.core") 456local sq = swiglib("mysql.core") 457local sq = swiglib("mysql.core","5.6") 458 459ffilib("libmysql","5.6.14") 460 461-- Watch out, the last one is less explicit and lacks the swiglib prefix. 462 463]]
--
464 465
do
466 467
local
isfile
=
lfs
.
isfile
468
local
report
=
logs
.
reporter
(
"
resolvers
"
,
"
lib
"
)
469
local
trace
=
false
470 471
trackers
.
register
(
"
resolvers.lib
"
,
function
(
v
)
trace
=
v
end
)
472 473
local
function
action
(
filename
)
474
return
isfile
(
filename
)
and
filename
or
false
475
end
476 477
function
resolvers
.
findlib
(
required
)
-- todo: cache
478
local
list
=
directives
.
value
(
"
system.librarynames
"
)
479
local
only
=
nameonly
(
required
)
480
if
type
(
list
)
=
=
"
table
"
then
481
list
=
list
[
only
]
482
if
type
(
list
)
=
=
"
table
"
then
483
if
trace
then
484
report
(
"
using lookup list for library %a: % | t
"
,
only
,
list
)
485
end
486
else
487
list
=
{
only
}
488
end
489
else
490
list
=
{
only
}
491
end
492
for
i
=
1
,
#
list
do
493
local
name
=
list
[
i
]
494
local
found
=
locate
(
name
,
false
,
trace
,
report
,
action
)
495
if
found
then
496
return
found
497
end
498
end
499
local
getpaths
=
resolvers
.
expandedpathlistfromvariable
500
if
getpaths
then
501
local
list
=
getpaths
(
"
PATH
"
)
502
local
base
=
addsuffix
(
only
,
os
.
libsuffix
)
503
for
i
=
1
,
#
list
do
504
local
full
=
joinfile
(
list
[
i
]
,
base
)
505
local
found
=
locate
(
full
,
false
,
trace
,
report
,
action
)
506
if
found
then
507
return
found
508
end
509
end
510
end
511
end
512 513
end
514