View source with raw comments or as raw
    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-2024, 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:- use_module(library(apply), [convlist/3, maplist/3]).   42:- use_module(library(filesex), [copy_file/2]).   43:- use_module(library(lists), [select/3, append/3, member/2]).   44:- use_module(library(option), [option/2, option/3]).   45:- use_module(library(pairs), [group_pairs_by_key/2]).   46:- use_module(library(pprint), [print_term/2]).   47:- use_module(library(prolog_code), [pi_head/2]).   48:- use_module(library(prolog_source),
   49              [ file_name_on_path/2,
   50                path_segments_atom/2,
   51                prolog_open_source/2,
   52                prolog_read_source_term/4,
   53                prolog_close_source/1
   54              ]).   55:- use_module(library(prolog_xref),
   56              [ xref_source/1,
   57                xref_module/2,
   58                xref_called/4,
   59                xref_defined/3,
   60                xref_built_in/1
   61              ]).   62:- use_module(library(readutil), [read_file_to_string/3]).   63:- use_module(library(solution_sequences), [distinct/2]).

Compute file dependencies

This module computes file dependencies for modules as a set of directives. */

   71:- multifile user:file_search_path/2.   72
   73user:file_search_path(noautoload, library(.)).
   74user:file_search_path(noautoload, library(semweb)).
   75user:file_search_path(noautoload, library(lynx)).
   76user:file_search_path(noautoload, library(tipc)).
   77user:file_search_path(noautoload, library(cql)).
   78user:file_search_path(noautoload, library(http)).
   79user:file_search_path(noautoload, library(dcg)).
   80user:file_search_path(noautoload, library(unicode)).
   81user:file_search_path(noautoload, library(clp)).
   82user:file_search_path(noautoload, library(pce(prolog/lib))).
 file_autoload_directives(+File, -Directives, +Options) is det
Compute the dependencies as autoload/2 directives. Options
missing(+Bool)
If true (default false), only generate directives for called predicates that are not already imported.
directive(+Directive)
Directive to use for adding dependencies. Defined options are:
use_autoload/2
(Default). This uses use_module/2 for files that cannot be imported using use_autoload/2.
use_autoload/1
This uses use_module/1 for files that cannot be imported using use_autoload/1.
use_module/2
use_module/1
update(Old)
Updated an existing set of directives. The returned set of Directive starts with copies of Old. If a member of Old is autoload/2 or use_module/2, new dependencies are added at the end of this list. New dependent files are added after the modified copies of Old. Declared dependencies are never removed, even if no proof of usage is found.

If no directive(+Directive) option is provided a default is determined from the given directives.

  118file_autoload_directives(File, Directives, Options) :-
  119    xref_source(File),
  120    findall(Head, distinct(Head, undefined(File, Head, Options)), Missing),
  121    option(update(Old), Options, []),
  122    convlist(missing_autoload(File, Old), Missing, Pairs),
  123    keysort(Pairs, Pairs1),
  124    group_pairs_by_key(Pairs1, Grouped),
  125    directives(File, Grouped, Directives, Options).
 undefined(+File, -Callable, +Options)
Callable is called in File, but no definition can be found. If File is not a module file we consider other files that are not module files.
  133undefined(File, Undef, Options) :-
  134    xref_module(File, _),
  135    !,
  136    xref_called_cond(File, Undef, Cond),
  137    \+ (   available(File, Undef, How, Options),
  138           How \== plain_file
  139       ),
  140    included_if_defined(Cond, Undef),
  141    Undef \= (_:_).
  142undefined(File, Undef, Options) :-
  143    xref_called_cond(File, Undef, Cond),
  144    \+ available(File, Undef, _, Options),
  145    included_if_defined(Cond, Undef),
  146    Undef \= (_:_).
 included_if_defined(+Condition, +Callable) is semidet
  150included_if_defined(true, _)  :- !.
  151included_if_defined(false, _) :- !, fail.
  152included_if_defined(fail, _)  :- !, fail.
  153included_if_defined(current_predicate(Name/Arity), Callable) :-
  154    \+ functor(Callable, Name, Arity),
  155    !.
  156included_if_defined(\+ Cond, Callable) :-
  157    !,
  158    \+ included_if_defined(Cond, Callable).
  159included_if_defined((A,B), Callable) :-
  160    !,
  161    included_if_defined(A, Callable),
  162    included_if_defined(B, Callable).
  163included_if_defined((A;B), Callable) :-
  164    !,
  165    (   included_if_defined(A, Callable)
  166    ;   included_if_defined(B, Callable)
  167    ).
  168
  169xref_called_cond(Source, Callable, Cond) :-
  170    xref_called(Source, Callable, By, Cond),
  171    By \= Callable.                 % recursive calls
 available(+File, +Callable, -HowDefined, +Options)
True if Callable is available in File.
  177available(File, Called, How, Options) :-
  178    xref_defined(File, Called, How0),
  179    (   How0 = imported(_)
  180    ->  option(missing(true), Options)
  181    ;   true
  182    ),
  183    !,
  184    How = How0.
  185available(_, Called, How, _) :-
  186    built_in_predicate(Called),
  187    !,
  188    How = builtin.
  189available(_, Called, How, _) :-
  190    Called = _:_,
  191    defined(_, Called),
  192    !,
  193    How = module_qualified.
  194available(_, M:G, How, _) :-
  195    defined(ExportFile, G),
  196    xref_module(ExportFile, M),
  197    !,
  198    How = module_overruled.
  199available(_, Called, How, _) :-
  200    defined(ExportFile, Called),
  201    \+ xref_module(ExportFile, _),
  202    !,
  203    How == plain_file.
 built_in_predicate(+Callable)
True if Callable is a built-in
  209built_in_predicate(Goal) :-
  210    strip_module(Goal, _, Plain),
  211    xref_built_in(Plain).
 defined(?File, ?Callable)
True if Callable is defined in File and not imported.
  217defined(File, Callable) :-
  218    xref_defined(File, Callable, How),
  219    How \= imported(_).
  220
  221
  222		 /*******************************
  223		 *       GENERATE OUTPUT	*
  224		 *******************************/
  225
  226missing_autoload(Src, _, Head, From-Head) :-
  227    xref_defined(Src, Head, imported(From)),
  228    !.
  229missing_autoload(Src, Directives, Head, File-Head) :-
  230    src_file(Src, SrcFile),
  231    member(:-(Dir), Directives),
  232    directive_file(Dir, FileSpec),
  233    absolute_file_name(FileSpec, File,
  234                       [ file_type(prolog),
  235                         file_errors(fail),
  236                         relative_to(SrcFile),
  237                         access(read)
  238                       ]),
  239    exports(File, Exports),
  240    member(PI, Exports),
  241    is_pi(PI),
  242    pi_head(PI, Head),
  243    !.
  244missing_autoload(_Src, _, Head, File-Head) :-
  245    predicate_property(Head, autoload(File0)),
  246    !,
  247    (   absolute_file_name(File0, File,
  248                           [ access(read),
  249                             file_type(prolog),
  250                             file_errors(fail)
  251                           ])
  252    ->  true
  253    ;   File = File0
  254    ).
  255missing_autoload(_Src, _, Head, File-Head) :-
  256    noautoload(Head, File),
  257    !.
  258missing_autoload(_Src, _, Head, _) :-
  259    pi_head(PI, Head),
  260    print_message(warning,
  261                  error(existence_error(procedure, PI), _)),
  262    fail.
  263
  264:- if(exists_source(library(pce))).  265:- autoload(library(pce), [get/3]).  266src_file(@(Ref), File) =>
  267    get(?(@(Ref), file), absolute_path, File).
  268:- endif.  269src_file(File0, File) =>
  270    File = File0.
 directives(+File, +FileAndHeads, -Directives, +Options) is det
Assemble the final set of directives. Uses the option update(Old).
  276directives(File, FileAndHeads, Directives, Options) :-
  277    option(update(Old), Options, []),
  278    phrase(update_directives(Old, FileAndHeads, RestDeps, File),
  279           Directives, Rest),
  280    update_style(Old, Options, Options1),
  281    maplist(directive(Options1), RestDeps, Rest0),
  282    sort(Rest0, Rest).
  283
  284update_directives([], Deps, Deps, _) -->
  285    [].
  286update_directives([:-(H)|T], Deps0, Deps, File) -->
  287    { update_directive(File, H, Deps0, Deps1, Directive) },
  288    !,
  289    [ :-(Directive) ],
  290    update_directives(T, Deps1, Deps, File).
  291update_directives([H|T], Deps0, Deps, File) -->
  292    [ H ],
  293    update_directives(T, Deps0, Deps, File).
  294
  295update_directive(Src, Dir0, Deps0, Deps, Dir) :-
  296    src_file(Src, SrcFile),
  297    directive_file(Dir0, FileSpec),
  298    absolute_file_name(FileSpec, File,
  299                       [ file_type(prolog),
  300                         file_errors(fail),
  301                         relative_to(SrcFile),
  302                         access(read)
  303                       ]),
  304    select(DepFile-Heads, Deps0, Deps),
  305    same_dep_file(DepFile, File),
  306    !,
  307    (   Dir0 =.. [Pred,File0,Imports]
  308    ->  exports(File, Exports),
  309        maplist(head_pi(Exports), Heads, PIs),
  310        subtract_pis(PIs, Imports, New),
  311        append(Imports, New, NewImports),
  312        Dir =.. [Pred,File0,NewImports]
  313    ;   Dir = Dir0
  314    ).
  315
  316directive_file(use_module(File),   File).
  317directive_file(use_module(File,_), File).
  318directive_file(autoload(File),     File).
  319directive_file(autoload(File,_),   File).
  320
  321same_dep_file(File, File) :-
  322    !.
  323same_dep_file(Dep, _File) :-
  324    exists_file(Dep),
  325    !,
  326    fail.
  327same_dep_file(Dep, File) :-
  328    user:prolog_file_type(Ext, prolog),
  329    file_name_extension(Dep, Ext, DepFile),
  330    same_file(DepFile, File),
  331    !.
  332
  333exports(File, Public) :-
  334    E = error(_,_),
  335    catch('$autoload':exports(File, _Module, Public), E,
  336          (   print_message(warning, E),
  337              Public = []
  338          )).
  339
  340is_pi(Name/Arity), atom(Name), integer(Arity) => true.
  341is_pi(Name//Arity), atom(Name), integer(Arity) => true.
  342is_pi(_) => fail.
 head_pi(+Exports, +Head, -PI) is det
  346head_pi(PIs, Head, PI) :-
  347    head_pi(Head, PI),
  348    memberchk(PI, PIs),
  349    !.
  350head_pi(_PIs, Head, PI) :-
  351    pi_head(PI, Head).
  352
  353head_pi(Head, PI) :-
  354    pi_head(PI0, Head),
  355    (   PI = PI0
  356    ;   dcg_pi(PI0, PI)
  357    ).
  358
  359dcg_pi(Module:Name/Arity, PI), integer(Arity), Arity >= 2 =>
  360    DCGArity is Arity - 2,
  361    PI = Module:Name//DCGArity.
  362dcg_pi(Name/Arity, PI), integer(Arity), Arity >= 2 =>
  363    DCGArity is Arity - 2,
  364    PI = Name//DCGArity.
  365dcg_pi(_/Arity, _), integer(Arity) =>
  366    fail.
 subtract_pis(+Set, +Delete, -Result) is det
  370subtract_pis([], _, R) =>
  371    R = [].
  372subtract_pis([H|T], D, R) =>
  373    (   member(E, D),
  374        same_pi(H, E)
  375    ->  subtract_pis(T, D, R)
  376    ;   R = [H|R1],
  377        subtract_pis(T, D, R1)
  378    ).
  379
  380same_pi(PI, PI) => true.
  381same_pi(Name/A1, Name//A2) => A1 =:= A2+2.
  382same_pi(Name//A1, Name/A2) => A1 =:= A2-2.
  383same_pi(_,_) => fail.
 update_style(+OldDirectives, +Options0, -Options)
Determine the directive to use for new dependencies. This establishes a default based on existing dependencies.
  391update_style(_Old, Options, Options) :-
  392    option(directive(_), Options),
  393    !.
  394update_style(Old, Options, [directive(autoload/2)|Options]) :-
  395    memberchk((:- autoload(_,_)), Old),
  396    !.
  397update_style(Old, Options, [directive(autoload/1)|Options]) :-
  398    memberchk((:- autoload(_)), Old),
  399    !.
  400update_style(Old, Options, [directive(use_module/2)|Options]) :-
  401    memberchk((:- use_module(_,_)), Old),
  402    !.
  403update_style(Old, Options, [directive(use_module/1)|Options]) :-
  404    memberchk((:- use_module(_)), Old),
  405    !.
  406update_style(_, Options, Options).
 directive(+Options, +FileAndHeads, -Directive)
Create a directive to import Heads from File.
  413directive(Options, File-Heads, Directive) :-
  414    file_name_extension(File, pl, LibFile),
  415    file_name_on_path(LibFile, Lib0),
  416    segments(Lib0, Lib),
  417    maplist(pi_head, PIs, Heads),
  418    make_directive(Lib, PIs, Directive, Options).
  419
  420segments(Term0, Term) :-
  421    Term0 =.. [Alias,Atom],
  422    path_segments_atom(Segments, Atom),
  423    format(atom(Atom), '~q', [Segments]),
  424    !,
  425    Term =.. [Alias,Segments].
  426segments(FilePL, File) :-
  427    atom(FilePL),
  428    file_name_extension(File, pl, FilePL),
  429    !.
  430segments(Term, Term).
  431
  432:- multifile
  433    prolog:no_autoload_module/1.  434
  435make_directive(Lib, Import, (:- use_module(Lib, Import)), Options) :-
  436    option(directive(use_module/2), Options, use_autoload/2),
  437    !.
  438make_directive(Lib, _Import, (:- use_module(Lib)), Options) :-
  439    option(directive(use_module/1), Options, use_autoload/2),
  440    !.
  441make_directive(Lib, _Import, (:- use_module(Lib)), Options) :-
  442    option(directive(use_autoload/1), Options, use_autoload/2),
  443    prolog:no_autoload_module(Lib),
  444    !.
  445make_directive(Lib, Import, (:- use_module(Lib, Import)), _) :-
  446    prolog:no_autoload_module(Lib),
  447    !.
  448make_directive(Lib, _Import, (:- autoload(Lib)), Options) :-
  449    option(directive(use_autoload/1), Options, use_autoload/2),
  450    !.
  451make_directive(Lib, Import, (:- autoload(Lib, Import)), _).
  452
  453
  454		 /*******************************
  455		 *          NO AUTOLOAD		*
  456		 *******************************/
  457
  458:- dynamic
  459    library_index/3,                % Head x Module x Path
  460    autoload_directories/1,         % List
  461    index_checked_at/1.             % Time
  462:- volatile
  463    library_index/3,
  464    autoload_directories/1,
  465    index_checked_at/1.  466
  467noautoload(Head, File) :-
  468    functor(Head, Name, Arity),
  469    context_module(Here),
  470    '$autoload':load_library_index(Here:Name, Arity, Here:noautoload('INDEX')),
  471    library_index(Head, _, File).
  472
  473
  474		 /*******************************
  475		 *           REPLACE		*
  476		 *******************************/
 file_auto_import(+File, +Options)
Update the autoload/2 directives for File. This predicate modifies the file in place. Defined options are:
backup(+Extension)
Create a backup of File using Extension.
  486file_auto_import(File, Options) :-
  487    absolute_file_name(File, Path,
  488                       [ file_type(prolog),
  489                         access(read)
  490                       ]),
  491    file_autoload_directives(Path, Directives, Options),
  492    (   option(backup(Ext), Options)
  493    ->  file_name_extension(Path, Ext, Old),
  494        copy_file(Path, Old)
  495    ;   true
  496    ),
  497    Edit = _{import:Directives, done:_},
  498    (   has_import(Path)
  499    ->  edit_file(Old, Path, Edit.put(replace,true))
  500    ;   edit_file(Old, Path, Edit.put(new,true))
  501    ).
  502
  503has_import(InFile) :-
  504    setup_call_cleanup(
  505        prolog_open_source(InFile, In),
  506        (   repeat,
  507            prolog_read_source_term(In, Term, _Expanded, []),
  508            (   Term == end_of_file
  509            ->  !
  510            ;    true
  511            )
  512        ),
  513        prolog_close_source(In)),
  514    nonvar(Term),
  515    import_directive(Term),
  516    !.
  517
  518import_directive((:- use_module(_))).
  519import_directive((:- use_module(_, _))).
 rewrite_term(+In, -Keep, -OutList, +Options) is semidet
  523rewrite_term(Never,_,_,_) :-
  524    never_rewrite(Never),
  525    !,
  526    fail.
  527rewrite_term(Import,false,[],Options) :-
  528    Options.done == true,
  529    !,
  530    import_directive(Import).
  531rewrite_term(In,false,Directives,Options) :-
  532    import_directive(In),
  533    !,
  534    append(Options.import, [nl], Directives),
  535    Options.done = true.
  536rewrite_term(In,true,Directives,Options) :-
  537    In = (:- module(_,_)),
  538    Options.get(new) == true,
  539    !,
  540    append(Options.import, [nl], Directives),
  541    Options.done = true.
  542
  543never_rewrite((:- use_module(_, []))).
  544
  545edit_file(InFile, OutFile, Options) :-
  546    read_file_to_string(InFile, String, []),
  547    setup_call_cleanup(
  548        prolog_open_source(InFile, In),
  549        setup_call_cleanup(
  550            open(OutFile, write, Out),
  551            rewrite(In, Out, String, Options),
  552            close(Out)),
  553        prolog_close_source(In)).
  554
  555rewrite(In, Out, String, Options) :-
  556    prolog_read_source_term(
  557        In, Term, _Expanded,
  558        [ term_position(StartPos),
  559          subterm_positions(TermPos),
  560          comments(Comments)
  561        ]),
  562    stream_position_data(char_count, StartPos, StartChar),
  563    copy_comments(Comments, StartChar, String, Out),
  564    (   Term == end_of_file
  565    ->  true
  566    ;   (   nonvar(Term),
  567            rewrite_term(Term, Keep, List, Options)
  568        ->  (   Keep == true
  569            ->  copy_term_string(TermPos, String, Out)
  570            ;   true
  571            ),
  572            forall(member(T, List),
  573                   output_term(Out, T)),
  574            (   append(_, [nl], List)
  575            ->  skip_blanks(In)
  576            ;   true
  577            )
  578        ;   copy_term_string(TermPos, String, Out)
  579        ),
  580        rewrite(In, Out, String, Options)
  581    ).
  582
  583output_term(Out, nl) :-
  584    !,
  585    nl(Out).
  586output_term(Out, Term) :-
  587    print_term(Term, [output(Out)]),
  588    format(Out, '.~n', []).
  589
  590copy_comments([Pos-H|T], StartChar, String, Out) :-
  591    stream_position_data(char_count, Pos, Start),
  592    Start < StartChar,
  593    !,
  594    string_length(H, Len),
  595    sub_string(String, Start, Len, _, Comment),
  596    End is Start+Len+1,
  597    layout_after(End, String, Layout),
  598    format(Out, '~s~s', [Comment, Layout]),
  599    copy_comments(T, StartChar, String, Out).
  600copy_comments(_, _, _, _).
  601
  602copy_term_string(TermPos, String, Out) :-
  603    arg(1, TermPos, Start),
  604    arg(2, TermPos, End),
  605    Len is End - Start,
  606    sub_string(String, Start, Len, _, TermString),
  607    End1 is End + 1,
  608    full_stop_after(End1, String, Layout),
  609    format(Out, '~s~s', [TermString, Layout]).
  610
  611layout_after(Index, String, [H|T]) :-
  612    string_code(Index, String, H),
  613    code_type(H, space),
  614    !,
  615    Index2 is Index+1,
  616    layout_after(Index2, String, T).
  617layout_after(_, _, []).
  618
  619full_stop_after(Index, String, [H|T]) :-
  620    string_code(Index, String, H),
  621    Index2 is Index+1,
  622    (   code_type(H, space)
  623    ->  !, full_stop_after(Index2, String, T)
  624    ;   H == 0'.
  625    ->  !, layout_after(Index2, String, T)
  626    ).
  627full_stop_after(_, _, []).
  628
  629skip_blanks(In) :-
  630    peek_code(In, C),
  631    code_type(C, space),
  632    !,
  633    get_code(In, _),
  634    skip_blanks(In).
  635skip_blanks(_)