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(<, -, _) => fail.
207cmp_git_version(>, -, _) => fail.
208cmp_git_version(>, _, -) => true.
209cmp_git_version(=, -, _) => true.
210cmp_git_version(=, _, -) => true.
211cmp_git_version(=, git(V,-), git(V,_)) => true.
212cmp_git_version(=, git(V,_), git(V,-)) => true.
213cmp_git_version(<, git(V1, _V1_Hash), git(V2, _V2_Hash)) =>
214 V1 < V2.
215cmp_git_version(>, git(V1, _V1_Hash), git(V2, _V2_Hash)) =>
216 V1 > V2.
217cmp_git_version(=, V1, V2) => V1 == V2.
218
220
221parse_version(Spec, VNumbers, GitVersion, Cmp, VString) :-
222 spec_cmp_version(Spec, Cmp, VString),
223 parse_version(VString, VNumbers, GitVersion).
224
225spec_cmp_version(Spec, Cmp, Version),
226 compound(Spec), compound_name_arity(Spec, Cmp, 1) =>
227 ( is_cmp(Cmp)
228 -> true
229 ; domain_error(comparison_operator, Cmp)
230 ),
231 arg(1, Spec, Version).
232spec_cmp_version(Spec, Cmp, Version), atom(Spec) =>
233 Cmp = (>=),
234 Version = Spec.
235spec_cmp_version(Spec, Cmp, Version), string(Spec) =>
236 Cmp = (>=),
237 atom_string(Version, Spec).
238spec_cmp_version(Spec, _Cmp, _Version) =>
239 type_error(version, Spec).
240
241is_cmp(=<).
242is_cmp(<).
243is_cmp(>=).
244is_cmp(>).
245is_cmp(=).
246is_cmp(>=).
247
248parse_version(String, VNumbers, VGit) :-
249 ( parse_version_(String, VNumbers, VGit)
250 -> true
251 ; domain_error(version_string, String)
252 ).
253
254parse_version_(String, VNumbers, git(GitRev, GitHash)) :-
255 split_string(String, "-", "", [NumberS,GitRevS|Hash]),
256 !,
257 split_string(NumberS, ".", "", List),
258 maplist(number_string, VNumbers, List),
259 ( GitRevS == "DIRTY"
260 -> GitRev = 0,
261 GitHash = 'DIRTY'
262 ; number_string(GitRev, GitRevS),
263 ( Hash = [HashS]
264 -> atom_string(GitHash, HashS)
265 ; GitHash = '-'
266 )
267 ).
268parse_version_(String, VNumbers, -) :-
269 split_string(String, ".", "", List),
270 maplist(number_string, VNumbers, List).
271
275
276check_feature(warning(Flag)) :-
277 !,
278 ( has_feature(Flag)
279 -> true
280 ; print_message(
281 warning,
282 error(existence_error(prolog_feature, warning(Flag)), _))
283 ).
284check_feature(Flag) :-
285 has_feature(Flag),
286 !.
287check_feature(Flag) :-
288 existence_error(prolog_feature, Flag).
289
290has_feature(rational) =>
291 current_prolog_flag(bounded, false).
292has_feature(library(Lib)) =>
293 exists_source(library(Lib)).
294has_feature(Flag), atom(Flag) =>
295 current_prolog_flag(Flag, true).
296has_feature(Flag), Flag =.. [Name|Arg] =>
297 current_prolog_flag(Name, Arg).
298
299 302
303:- multifile
304 prolog:error_message//1. 305
306prolog:error_message(version_error(Component, Found, Required)) -->
307 { current_prolog_flag(executable, Exe) },
308 [ 'Application requires ~w '-[Component] ], req_msg(Required),
309 [ ',', nl, ' ',
310 ansi(code, '~w', [Exe]), ' has version ',
311 ansi(code, '~w', [Found])
312 ].
313prolog:error_message(existence_error(prolog_feature, Feature)) -->
314 missing_feature(Feature).
315
316req_msg((A,B)) --> req_msg(A), [' and '], req_msg(B).
317req_msg((A;B)) --> req_msg(A), [' or '], req_msg(B).
318req_msg(\+(A)) --> ['not '], req_msg(A).
319req_msg(V) --> { spec_cmp_version(V, Cmp, Version) }, !, cmp_msg(Cmp), [' '],
320 [ ansi(code, '~w', [Version]) ].
321
322cmp_msg(<) --> ['before'].
323cmp_msg(=<) --> ['at most'].
324cmp_msg(=) --> ['exactly'].
325cmp_msg(>=) --> ['at least'].
326cmp_msg(>) --> ['after'].
327
328missing_feature(warning(Feature)) -->
329 [ 'This version of SWI-Prolog does not optimally support your \c
330 application because',
331 nl, ' '
332 ],
333 missing_feature_(Feature).
334missing_feature(warning(Feature)) -->
335 [ 'This version of SWI-Prolog cannot run your application because',
336 nl, ' '
337 ],
338 missing_feature_(Feature).
339
340missing_feature_(threads) -->
341 [ 'multi-threading is not available' ].
342missing_feature_(rational) -->
343 [ 'it has no support for rational numbers' ].
344missing_feature_(bounded(false)) -->
345 [ 'it has no support for unbounded arithmetic' ].
346missing_feature_(library(Lib)) -->
347 [ 'it does not provide library(~q)'-[Lib] ].
348missing_feature_(Feature) -->
349 [ 'it does not support ~p'-[Feature] ]