1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2020-2026, VU University Amsterdam 7 CWI, Amsterdam 8 SWI-Prolog Solutions b.v. 9 All rights reserved. 10 11 Redistribution and use in source and binary forms, with or without 12 modification, are permitted provided that the following conditions 13 are met: 14 15 1. Redistributions of source code must retain the above copyright 16 notice, this list of conditions and the following disclaimer. 17 18 2. Redistributions in binary form must reproduce the above copyright 19 notice, this list of conditions and the following disclaimer in 20 the documentation and/or other materials provided with the 21 distribution. 22 23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 POSSIBILITY OF SUCH DAMAGE. 35*/ 36 37:- module(prolog_deps, 38 [ file_autoload_directives/3, % +File, -Directives, +Options 39 file_auto_import/2 % +File, +Options 40 ]). 41:- autoload(library(apply), [convlist/3, maplist/3, exclude/3]). 42:- if(exists_source(library(filesex))). 43:- autoload(library(filesex), [copy_file/2]). 44:- endif. 45:- autoload(library(lists), [select/3, append/3, member/2]). 46:- autoload(library(option), [option/2, option/3]). 47:- autoload(library(pairs), [group_pairs_by_key/2]). 48:- autoload(library(pprint), [print_term/2]). 49:- autoload(library(prolog_code), [pi_head/2]). 50:- autoload(library(prolog_source), 51 [ file_name_on_path/2, 52 path_segments_atom/2, 53 prolog_open_source/2, 54 prolog_read_source_term/4, 55 prolog_close_source/1 56 ]). 57:- autoload(library(prolog_xref), 58 [ xref_source/1, 59 xref_module/2, 60 xref_called/4, 61 xref_defined/3, 62 xref_built_in/1, 63 xref_public_list/3 64 ]). 65:- autoload(library(readutil), [read_file_to_string/3]). 66:- autoload(library(solution_sequences), [distinct/2]). 67:- autoload(library(error), [existence_error/2]).
75:- multifile user:file_search_path/2. 76 77user:file_search_path(noautoload, library(.)). 78user:file_search_path(noautoload, library(semweb)). 79user:file_search_path(noautoload, library(lynx)). 80user:file_search_path(noautoload, library(tipc)). 81user:file_search_path(noautoload, library(cql)). 82user:file_search_path(noautoload, library(http)). 83user:file_search_path(noautoload, library(dcg)). 84user:file_search_path(noautoload, library(unicode)). 85user:file_search_path(noautoload, library(clp)). 86user:file_search_path(noautoload, library(pce(prolog/lib))).
true (default false), only generate directives
for called predicates that are not already imported.
If no directive(+Directive) option is provided a
default is determined from the given directives.
122file_autoload_directives(File, Directives, Options) :-
123 xref_source(File),
124 findall(Head, distinct(Head, undefined(File, Head, Options)), Missing0),
125 clean_missing(Missing0, Missing),
126 option(update(Old), Options, []),
127 convlist(missing_autoload(File, Old), Missing, Pairs),
128 keysort(Pairs, Pairs1),
129 group_pairs_by_key(Pairs1, Grouped),
130 directives(File, Grouped, Directives, Options).138undefined(File, Undef, Options) :- 139 xref_module(File, _), 140 !, 141 xref_called_cond(File, Undef, Cond), 142 \+ ( available(File, Undef, How, Options), 143 How \== plain_file 144 ), 145 included_if_defined(Cond, Undef), 146 Undef \= (_:_). 147undefined(File, Undef, Options) :- 148 xref_called_cond(File, Undef, Cond), 149 \+ available(File, Undef, _, Options), 150 included_if_defined(Cond, Undef), 151 Undef \= (_:_).
155included_if_defined(true, _) :- !. 156included_if_defined(false, _) :- !, fail. 157included_if_defined(fail, _) :- !, fail. 158included_if_defined(current_predicate(Name/Arity), Callable) :- 159 \+ functor(Callable, Name, Arity), 160 !. 161included_if_defined(\+ Cond, Callable) :- 162 !, 163 \+ included_if_defined(Cond, Callable). 164included_if_defined((A,B), Callable) :- 165 !, 166 included_if_defined(A, Callable), 167 included_if_defined(B, Callable). 168included_if_defined((A;B), Callable) :- 169 !, 170 ( included_if_defined(A, Callable) 171 ; included_if_defined(B, Callable) 172 ). 173 174xref_called_cond(Source, Callable, Cond) :- 175 xref_called(Source, Callable, By, Cond), 176 By \= Callable. % recursive calls
182available(File, Called, How, Options) :- 183 xref_defined(File, Called, How0), 184 ( How0 = imported(_) 185 -> option(missing(true), Options) 186 ; true 187 ), 188 !, 189 How = How0. 190available(_, Called, How, _) :- 191 built_in_predicate(Called), 192 !, 193 How = builtin. 194available(_, Called, How, _) :- 195 Called = _:_, 196 defined(_, Called), 197 !, 198 How = module_qualified. 199available(_, M:G, How, _) :- 200 defined(ExportFile, G), 201 xref_module(ExportFile, M), 202 !, 203 How = module_overruled. 204available(_, Called, How, _) :- 205 defined(ExportFile, Called), 206 \+ xref_module(ExportFile, _), 207 !, 208 How == plain_file.
214built_in_predicate(Goal) :-
215 strip_module(Goal, _, Plain),
216 xref_built_in(Plain).
222defined(File, Callable) :-
223 xref_defined(File, Callable, How),
224 How \= imported(_).232clean_missing(Missing0, Missing) :- 233 memberchk(main, Missing0), 234 memberchk(argv_options(_,_,_), Missing0), 235 !, 236 exclude(argv_option_hook, Missing0, Missing). 237clean_missing(Missing, Missing). 238 239argv_option_hook(opt_type(_,_,_)). 240argv_option_hook(opt_help(_,_)). 241argv_option_hook(opt_meta(_,_)). 242 243 244 /******************************* 245 * GENERATE OUTPUT * 246 *******************************/ 247 248missing_autoload(Src, _, Head, From-Head) :- 249 xref_defined(Src, Head, imported(From)), 250 !. 251missing_autoload(Src, Directives, Head, File-Head) :- 252 src_file(Src, SrcFile), 253 member(:-(Dir), Directives), 254 directive_file(Dir, FileSpec), 255 absolute_file_name(FileSpec, File, 256 [ file_type(prolog), 257 file_errors(fail), 258 relative_to(SrcFile), 259 access(read) 260 ]), 261 xref_public_list(File, SrcFile, [exports(Exports)]), 262 member(PI, Exports), 263 is_pi(PI), 264 pi_head(PI, Head), 265 !. 266missing_autoload(_Src, _, Head, File-Head) :- 267 predicate_property(Head, autoload(File0)), 268 !, 269 ( absolute_file_name(File0, File1, 270 [ access(read), 271 file_type(prolog), 272 file_errors(fail) 273 ]) 274 -> qlf_pl_file(File1, File) 275 ; File = File0 276 ). 277missing_autoload(_Src, _, Head, File-Head) :- 278 noautoload(Head, File), 279 !. 280missing_autoload(_Src, _, Head, _) :- 281 pi_head(PI, Head), 282 print_message(warning, 283 error(existence_error(procedure, PI), _)), 284 fail. 285 286:- if(exists_source(library(pce))). 287:- autoload(library(pce), [get/3,object/1]). 288src_file(Ref, File) => 289 object(Ref), 290 get(?(Ref, file), absolute_path, File). 291:- endif. 292src_file(File0, File) => 293 File = File0.
update(Old).299directives(File, FileAndHeads, Directives, Options) :- 300 option(update(Old), Options, []), 301 phrase(update_directives(Old, FileAndHeads, RestDeps, File), 302 Directives, Rest), 303 update_style(Old, Options, Options1), 304 maplist(directive(Options1), RestDeps, Rest0), 305 sort(Rest0, Rest). 306 307update_directives([], Deps, Deps, _) --> 308 []. 309update_directives([:-(H)|T], Deps0, Deps, File) --> 310 { update_directive(File, H, Deps0, Deps1, Directive) }, 311 !, 312 [ :-(Directive) ], 313 update_directives(T, Deps1, Deps, File). 314update_directives([H|T], Deps0, Deps, File) --> 315 [ H ], 316 update_directives(T, Deps0, Deps, File). 317 318update_directive(Src, Dir0, Deps0, Deps, Dir) :- 319 src_file(Src, SrcFile), 320 directive_file(Dir0, FileSpec), 321 absolute_file_name(FileSpec, File, 322 [ file_type(prolog), 323 file_errors(fail), 324 relative_to(SrcFile), 325 access(read) 326 ]), 327 qlf_pl_file(File, PlFile), 328 select(DepFile-Heads, Deps0, Deps), 329 same_dep_file(DepFile, PlFile), 330 !, 331 ( Dir0 =.. [Pred,File0,Imports] 332 -> xref_public_list(PlFile, SrcFile, [exports(Exports)]), 333 maplist(head_pi(Exports), Heads, PIs), 334 subtract_pis(PIs, Imports, New), 335 append(Imports, New, NewImports), 336 Dir =.. [Pred,File0,NewImports] 337 ; Dir = Dir0 338 ). 339 340directive_file(use_module(File), File). 341directive_file(use_module(File,_), File). 342directive_file(autoload(File), File). 343directive_file(autoload(File,_), File). 344 345qlf_pl_file(File, PlFile) :- 346 file_name_extension(_Base, Ext, File), 347 user:prolog_file_type(Ext, qlf), 348 !, 349 '$qlf_module'(File, Info), 350 PlFile = Info.get(file). 351qlf_pl_file(File, File). 352 353same_dep_file(File, File) :- 354 !. 355same_dep_file(Dep, _File) :- 356 exists_file(Dep), 357 !, 358 fail. 359same_dep_file(Dep, File) :- 360 user:prolog_file_type(Ext, prolog), 361 file_name_extension(Dep, Ext, DepFile), 362 same_file(DepFile, File), 363 !. 364 365is_pi(Name/Arity), atom(Name), integer(Arity) => true. 366is_pi(Name//Arity), atom(Name), integer(Arity) => true. 367is_pi(_) => fail.
371head_pi(PIs, Head, PI) :- 372 head_pi(Head, PI), 373 memberchk(PI, PIs), 374 !. 375head_pi(_PIs, Head, PI) :- 376 pi_head(PI, Head). 377 378head_pi(Head, PI) :- 379 pi_head(PI0, Head), 380 ( PI = PI0 381 ; dcg_pi(PI0, PI) 382 ). 383 384dcg_pi(Module:Name/Arity, PI), integer(Arity), Arity >= 2 => 385 DCGArity is Arity - 2, 386 PI = Module:Name//DCGArity. 387dcg_pi(Name/Arity, PI), integer(Arity), Arity >= 2 => 388 DCGArity is Arity - 2, 389 PI = Name//DCGArity. 390dcg_pi(_/Arity, _), integer(Arity) => 391 fail.
395subtract_pis([], _, R) => 396 R = []. 397subtract_pis([H|T], D, R) => 398 ( member(E, D), 399 same_pi(H, E) 400 -> subtract_pis(T, D, R) 401 ; R = [H|R1], 402 subtract_pis(T, D, R1) 403 ). 404 405same_pi(PI, PI) => true. 406same_pi(Name/A1, Name//A2) => A1 =:= A2+2. 407same_pi(Name//A1, Name/A2) => A1 =:= A2-2. 408same_pi(_,_) => fail.
416update_style(_Old, Options, Options) :- 417 option(directive(_), Options), 418 !. 419update_style(Old, Options, [directive(autoload/2)|Options]) :- 420 memberchk((:- autoload(_,_)), Old), 421 !. 422update_style(Old, Options, [directive(autoload/1)|Options]) :- 423 memberchk((:- autoload(_)), Old), 424 !. 425update_style(Old, Options, [directive(use_module/2)|Options]) :- 426 memberchk((:- use_module(_,_)), Old), 427 !. 428update_style(Old, Options, [directive(use_module/1)|Options]) :- 429 memberchk((:- use_module(_)), Old), 430 !. 431update_style(_, Options, Options).
438directive(Options, File-Heads, Directive) :- 439 file_name_extension(File, pl, LibFile), 440 file_name_on_path(LibFile, Lib0), 441 segments(Lib0, Lib), 442 maplist(pi_head, PIs, Heads), 443 make_directive(Lib, PIs, Directive, Options). 444 445segments(Term0, Term) :- 446 Term0 =.. [Alias,Atom], 447 path_segments_atom(Segments, Atom), 448 format(atom(Atom), '~q', [Segments]), 449 !, 450 Term =.. [Alias,Segments]. 451segments(FilePL, File) :- 452 atom(FilePL), 453 file_name_extension(File, pl, FilePL), 454 !. 455segments(Term, Term). 456 457:- multifile 458 prolog:no_autoload_module/1. 459 460make_directive(Lib, Import, (:- use_module(Lib, Import)), Options) :- 461 option(directive(use_module/2), Options, use_autoload/2), 462 !. 463make_directive(Lib, _Import, (:- use_module(Lib)), Options) :- 464 option(directive(use_module/1), Options, use_autoload/2), 465 !. 466make_directive(Lib, _Import, (:- use_module(Lib)), Options) :- 467 option(directive(use_autoload/1), Options, use_autoload/2), 468 prolog:no_autoload_module(Lib), 469 !. 470make_directive(Lib, Import, (:- use_module(Lib, Import)), _) :- 471 prolog:no_autoload_module(Lib), 472 !. 473make_directive(Lib, _Import, (:- autoload(Lib)), Options) :- 474 option(directive(use_autoload/1), Options, use_autoload/2), 475 !. 476make_directive(Lib, Import, (:- autoload(Lib, Import)), _). 477 478 479 /******************************* 480 * NO AUTOLOAD * 481 *******************************/ 482 483:- dynamic 484 library_index/3, % Head x Module x Path 485 autoload_directories/1, % List 486 index_checked_at/1. % Time 487:- volatile 488 library_index/3, 489 autoload_directories/1, 490 index_checked_at/1.
498noautoload(Head, File) :- 499 functor(Head, Name, Arity), 500 functor(GenHead, Name, Arity), 501 context_module(Here), 502 '$autoload':load_library_index(Here:Name, Arity, Here:noautoload('INDEX')), 503 library_index(GenHead, _, File), 504 !. 505 506 507 /******************************* 508 * REPLACE * 509 *******************************/
519file_auto_import(File, Options) :- 520 absolute_file_name(File, Path, 521 [ file_type(prolog), 522 access(read) 523 ]), 524 file_autoload_directives(Path, Directives, Options), 525 ( option(backup(Ext), Options) 526 -> file_name_extension(Path, Ext, Old), 527 copy_file_ext(Path, Old) 528 ; true 529 ), 530 Edit = _{import:Directives, done:_}, 531 ( has_import(Path) 532 -> edit_file(Old, Path, Edit.put(replace,true)) 533 ; edit_file(Old, Path, Edit.put(new,true)) 534 ). 535 536:- if(current_predicate(copy_file/2)). 537copy_file_ext(From, To) :- 538 copy_file(From, To). 539:- else. 540copy_file_ext(_From, _To) :- 541 existence_error(predicate, copy_file/2). 542:- endif. 543 544has_import(InFile) :- 545 setup_call_cleanup( 546 prolog_open_source(InFile, In), 547 ( repeat, 548 prolog_read_source_term(In, Term, _Expanded, []), 549 ( Term == end_of_file 550 -> ! 551 ; true 552 ) 553 ), 554 prolog_close_source(In)), 555 nonvar(Term), 556 import_directive(Term), 557 !. 558 559import_directive((:- use_module(_))). 560import_directive((:- use_module(_, _))).
564rewrite_term(Never,_,_,_) :- 565 never_rewrite(Never), 566 !, 567 fail. 568rewrite_term(Import,false,[],Options) :- 569 Options.done == true, 570 !, 571 import_directive(Import). 572rewrite_term(In,false,Directives,Options) :- 573 import_directive(In), 574 !, 575 append(Options.import, [nl], Directives), 576 Options.done = true. 577rewrite_term(In,true,Directives,Options) :- 578 In = (:- module(_,_)), 579 Options.get(new) == true, 580 !, 581 append(Options.import, [nl], Directives), 582 Options.done = true. 583 584never_rewrite((:- use_module(_, []))). 585 586edit_file(InFile, OutFile, Options) :- 587 read_file_to_string(InFile, String, []), 588 setup_call_cleanup( 589 prolog_open_source(InFile, In), 590 setup_call_cleanup( 591 open(OutFile, write, Out), 592 rewrite(In, Out, String, Options), 593 close(Out)), 594 prolog_close_source(In)). 595 596rewrite(In, Out, String, Options) :- 597 prolog_read_source_term( 598 In, Term, _Expanded, 599 [ term_position(StartPos), 600 subterm_positions(TermPos), 601 comments(Comments) 602 ]), 603 stream_position_data(char_count, StartPos, StartChar), 604 copy_comments(Comments, StartChar, String, Out), 605 ( Term == end_of_file 606 -> true 607 ; ( nonvar(Term), 608 rewrite_term(Term, Keep, List, Options) 609 -> ( Keep == true 610 -> copy_term_string(TermPos, String, Out) 611 ; true 612 ), 613 forall(member(T, List), 614 output_term(Out, T)), 615 ( append(_, [nl], List) 616 -> skip_blanks(In) 617 ; true 618 ) 619 ; copy_term_string(TermPos, String, Out) 620 ), 621 rewrite(In, Out, String, Options) 622 ). 623 624output_term(Out, nl) :- 625 !, 626 nl(Out). 627output_term(Out, Term) :- 628 print_term(Term, [output(Out)]), 629 format(Out, '.~n', []). 630 631copy_comments([Pos-H|T], StartChar, String, Out) :- 632 stream_position_data(char_count, Pos, Start), 633 Start < StartChar, 634 !, 635 string_length(H, Len), 636 sub_string(String, Start, Len, _, Comment), 637 End is Start+Len+1, 638 layout_after(End, String, Layout), 639 format(Out, '~s~s', [Comment, Layout]), 640 copy_comments(T, StartChar, String, Out). 641copy_comments(_, _, _, _). 642 643copy_term_string(TermPos, String, Out) :- 644 arg(1, TermPos, Start), 645 arg(2, TermPos, End), 646 Len is End - Start, 647 sub_string(String, Start, Len, _, TermString), 648 End1 is End + 1, 649 full_stop_after(End1, String, Layout), 650 format(Out, '~s~s', [TermString, Layout]). 651 652layout_after(Index, String, [H|T]) :- 653 string_code(Index, String, H), 654 code_type(H, space), 655 !, 656 Index2 is Index+1, 657 layout_after(Index2, String, T). 658layout_after(_, _, []). 659 660full_stop_after(Index, String, [H|T]) :- 661 string_code(Index, String, H), 662 Index2 is Index+1, 663 ( code_type(H, space) 664 -> !, full_stop_after(Index2, String, T) 665 ; H == 0'. 666 -> !, layout_after(Index2, String, T) 667 ). 668full_stop_after(_, _, []). 669 670skip_blanks(In) :- 671 peek_code(In, C), 672 code_type(C, space), 673 !, 674 get_code(In, _), 675 skip_blanks(In). 676skip_blanks(_)
Compute file dependencies
This module computes file dependencies for modules as a set of directives.