34
35:- module(prolog_versions,
36 [ require_prolog_version/2, 37 require_version/3, 38 cmp_versions/3 39 ]). 40:- autoload(library(apply), [maplist/2, maplist/3]). 41:- autoload(library(error), [domain_error/2, existence_error/2, type_error/2]). 42
55
98
99require_prolog_version(Required, Features) :-
100 require_prolog_version(Required),
101 maplist(check_feature, Features).
102
103require_prolog_version(Required) :-
104 prolog_version(Available),
105 require_version('SWI-Prolog', Available, Required).
106
107prolog_version(Version) :-
108 current_prolog_flag(version_git, Version),
109 !.
110prolog_version(Version) :-
111 current_prolog_flag(version_data, swi(Major, Minor, Patch, _)),
112 VNumbers = [Major, Minor, Patch],
113 atomic_list_concat(VNumbers, '.', Version).
114
121
122require_version(Component, Available, CmpRequired) :-
123 parse_version(Available, AvlNumbers, AvlGit),
124 ( require_version_(AvlNumbers, AvlGit, CmpRequired)
125 -> true
126 ; throw(error(version_error(Component, Available, CmpRequired), _))
127 ).
128
129require_version_(AvlNumbers, AvlGit, (V1;V2)) =>
130 ( require_version_(AvlNumbers, AvlGit, V1)
131 -> true
132 ; require_version_(AvlNumbers, AvlGit, V2)
133 ).
134require_version_(AvlNumbers, AvlGit, (V1,V2)) =>
135 ( require_version_(AvlNumbers, AvlGit, V1)
136 ; require_version_(AvlNumbers, AvlGit, V2)
137 ).
138require_version_(AvlNumbers, AvlGit, \+V1) =>
139 \+ require_version_(AvlNumbers, AvlGit, V1).
140require_version_(AvlNumbers, AvlGit, Required) =>
141 parse_version(Required, ReqNumbers, ReqGit, Cmp, _),
142 cmp_versions(Cmp, AvlNumbers, AvlGit, ReqNumbers, ReqGit).
143
153
154cmp_versions(Cmp, V1, V2) :-
155 parse_version(V1, V1_Numbers, V1_Git),
156 parse_version(V2, V2_Numbers, V2_Git),
157 ( nonvar(Cmp)
158 -> cmp_versions(Cmp, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
159 ; cmp_versions(<, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
160 -> Cmp = (<)
161 ; cmp_versions(>, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
162 -> Cmp = (<)
163 ; Cmp = (=)
164 ).
165
166cmp_versions(=<, V1_Numbers, V1_Git, V2_Numbers, V2_Git) =>
167 ( cmp_versions(<, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
168 -> true
169 ; cmp_versions(=, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
170 ).
171cmp_versions(>=, V1_Numbers, V1_Git, V2_Numbers, V2_Git) =>
172 ( cmp_versions(>, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
173 -> true
174 ; cmp_versions(=, V1_Numbers, V1_Git, V2_Numbers, V2_Git)
175 ).
176cmp_versions(<, V1_Numbers, V1_Git, V2_Numbers, V2_Git) =>
177 ( cmp_num_version(<, V1_Numbers, V2_Numbers)
178 -> true
179 ; V1_Numbers == V2_Numbers,
180 cmp_git_version(<, V1_Git, V2_Git)
181 ).
182cmp_versions(>, V1_Numbers, V1_Git, V2_Numbers, V2_Git) =>
183 ( cmp_num_version(>, V1_Numbers, V2_Numbers)
184 -> true
185 ; V1_Numbers == V2_Numbers,
186 cmp_git_version(>, V1_Git, V2_Git)
187 ).
188cmp_versions(=, V1_Numbers, V1_Git, V2_Numbers, V2_Git) =>
189 cmp_num_version(=, V1_Numbers, V2_Numbers),
190 cmp_git_version(=, V1_Git, V2_Git).
191
192cmp_num_version(Cmp, V1_Numbers, V2_Numbers) :-
193 shortest(V1_Numbers, V2_Numbers, V1, V2),
194 compare(Cmp, V1, V2).
195
196shortest([H1|T1], [H2|T2], [H1|R1], [H2|R2]) :-
197 !,
198 shortest(T1, T2, R1, R2).
199shortest(_,_, [], []).
200
201
202cmp_git_version(<, -, -) => fail.
203cmp_git_version(>, -, -) => fail.
204cmp_git_version(=, -, -) => true.
205cmp_git_version(<, _, -) => true.
206cmp_git_version(>, -, _) => true.
207cmp_git_version(=, -, _) => true.
208cmp_git_version(=, _, -) => true.
209cmp_git_version(=, git(V,-), git(V,_)) => true.
210cmp_git_version(=, git(V,_), git(V,-)) => true.
211cmp_git_version(<, git(V1, _V1_Hash), git(V2, _V2_Hash)) =>
212 V1 < V2.
213cmp_git_version(>, git(V1, _V1_Hash), git(V2, _V2_Hash)) =>
214 V1 > V2.
215cmp_git_version(=, V1, V2) => V1 == V2.
216
218
219parse_version(Spec, VNumbers, GitVersion, Cmp, VString) :-
220 spec_cmp_version(Spec, Cmp, VString),
221 parse_version(VString, VNumbers, GitVersion).
222
223spec_cmp_version(Spec, Cmp, Version),
224 compound(Spec), compound_name_arity(Spec, Cmp, 1) =>
225 ( is_cmp(Cmp)
226 -> true
227 ; domain_error(comparison_operator, Cmp)
228 ),
229 arg(1, Spec, Version).
230spec_cmp_version(Spec, Cmp, Version), atom(Spec) =>
231 Cmp = (>=),
232 Version = Spec.
233spec_cmp_version(Spec, Cmp, Version), string(Spec) =>
234 Cmp = (>=),
235 atom_string(Version, Spec).
236spec_cmp_version(Spec, _Cmp, _Version) =>
237 type_error(version, Spec).
238
239is_cmp(=<).
240is_cmp(<).
241is_cmp(>=).
242is_cmp(>).
243is_cmp(=).
244is_cmp(>=).
245
246parse_version(String, VNumbers, VGit) :-
247 ( parse_version_(String, VNumbers, VGit)
248 -> true
249 ; domain_error(version_string, String)
250 ).
251
252parse_version_(String, VNumbers, git(GitRev, GitHash)) :-
253 split_string(String, "-", "", [NumberS,GitRevS|Hash]),
254 !,
255 split_string(NumberS, ".", "", List),
256 maplist(number_string, VNumbers, List),
257 ( GitRevS == "DIRTY"
258 -> GitRev = 0,
259 GitHash = 'DIRTY'
260 ; number_string(GitRev, GitRevS),
261 ( Hash = [HashS]
262 -> atom_string(GitHash, HashS)
263 ; GitHash = '-'
264 )
265 ).
266parse_version_(String, VNumbers, -) :-
267 split_string(String, ".", "", List),
268 maplist(number_string, VNumbers, List).
269
273
274check_feature(warning(Flag)) :-
275 !,
276 ( has_feature(Flag)
277 -> true
278 ; print_message(
279 warning,
280 error(existence_error(prolog_feature, warning(Flag)), _))
281 ).
282check_feature(Flag) :-
283 has_feature(Flag),
284 !.
285check_feature(Flag) :-
286 existence_error(prolog_feature, Flag).
287
288has_feature(rational) =>
289 current_prolog_flag(bounded, false).
290has_feature(library(Lib)) =>
291 exists_source(library(Lib)).
292has_feature(Flag), atom(Flag) =>
293 current_prolog_flag(Flag, true).
294has_feature(Flag), Flag =.. [Name|Arg] =>
295 current_prolog_flag(Name, Arg).
296
297 300
301:- multifile
302 prolog:error_message//1. 303
304prolog:error_message(version_error(Component, Found, Required)) -->
305 { current_prolog_flag(executable, Exe) },
306 [ 'Application requires ~w '-[Component] ], req_msg(Required),
307 [ ',', nl, ' ',
308 ansi(code, '~w', [Exe]), ' has version ',
309 ansi(code, '~w', [Found])
310 ].
311prolog:error_message(existence_error(prolog_feature, Feature)) -->
312 missing_feature(Feature).
313
314req_msg((A,B)) --> req_msg(A), [' and '], req_msg(B).
315req_msg((A;B)) --> req_msg(A), [' or '], req_msg(B).
316req_msg(\+(A)) --> ['not '], req_msg(A).
317req_msg(V) --> { spec_cmp_version(V, Cmp, Version) }, !, cmp_msg(Cmp), [' '],
318 [ ansi(code, '~w', [Version]) ].
319
320cmp_msg(<) --> ['before'].
321cmp_msg(=<) --> ['at most'].
322cmp_msg(=) --> ['exactly'].
323cmp_msg(>=) --> ['at least'].
324cmp_msg(>) --> ['after'].
325
326missing_feature(warning(Feature)) -->
327 [ 'This version of SWI-Prolog does not optimally support your \c
328 application because',
329 nl, ' '
330 ],
331 missing_feature_(Feature).
332missing_feature(warning(Feature)) -->
333 [ 'This version of SWI-Prolog cannot run your application because',
334 nl, ' '
335 ],
336 missing_feature_(Feature).
337
338missing_feature_(threads) -->
339 [ 'multi-threading is not available' ].
340missing_feature_(rational) -->
341 [ 'it has no support for rational numbers' ].
342missing_feature_(bounded(false)) -->
343 [ 'it has no support for unbounded arithmetic' ].
344missing_feature_(library(Lib)) -->
345 [ 'it does not provide library(~q)'-[Lib] ].
346missing_feature_(Feature) -->
347 [ 'it does not support ~p'-[Feature] ]