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)  2012-2018, VU University Amsterdam
    7                              CWI, Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(prolog_pack,
   37          [ pack_list_installed/0,
   38            pack_info/1,                % +Name
   39            pack_list/1,                % +Keyword
   40            pack_search/1,              % +Keyword
   41            pack_install/1,             % +Name
   42            pack_install/2,             % +Name, +Options
   43            pack_upgrade/1,             % +Name
   44            pack_rebuild/1,             % +Name
   45            pack_rebuild/0,             % All packages
   46            pack_remove/1,              % +Name
   47            pack_property/2,            % ?Name, ?Property
   48
   49            pack_url_file/2             % +URL, -File
   50          ]).   51:- use_module(library(apply)).   52:- use_module(library(error)).   53:- use_module(library(process)).   54:- use_module(library(option)).   55:- use_module(library(readutil)).   56:- use_module(library(lists)).   57:- use_module(library(filesex)).   58:- use_module(library(xpath)).   59:- use_module(library(settings)).   60:- use_module(library(uri)).   61:- use_module(library(http/http_open)).   62:- use_module(library(http/json)).   63:- use_module(library(http/http_client), []).   % plugin for POST support

A package manager for Prolog

The library(prolog_pack) provides the SWI-Prolog package manager. This library lets you inspect installed packages, install packages, remove packages, etc. It is complemented by the built-in attach_packs/0 that makes installed packages available as libaries.

See also
- Installed packages can be inspected using ?- doc_browser.
To be done
- Version logic
- Find and resolve conflicts
- Upgrade git packages
- Validate git packages
- Test packages: run tests from directory `test'. */
   81:- multifile
   82    environment/2.                          % Name, Value
   83
   84:- dynamic
   85    pack_requires/2,                        % Pack, Requirement
   86    pack_provides_db/2.                     % Pack, Provided
   87
   88
   89                 /*******************************
   90                 *          CONSTANTS           *
   91                 *******************************/
   92
   93:- setting(server, atom, 'http://www.swi-prolog.org/pack/',
   94           'Server to exchange pack information').   95
   96
   97                 /*******************************
   98                 *         PACKAGE INFO         *
   99                 *******************************/
 current_pack(?Pack) is nondet
True if Pack is a currently installed pack.
  105current_pack(Pack) :-
  106    '$pack':pack(Pack, _).
 pack_list_installed is det
List currently installed packages. Unlike pack_list/1, only locally installed packages are displayed and no connection is made to the internet.
See also
- Use pack_list/1 to find packages.
  116pack_list_installed :-
  117    findall(Pack, current_pack(Pack), Packages0),
  118    Packages0 \== [],
  119    !,
  120    sort(Packages0, Packages),
  121    length(Packages, Count),
  122    format('Installed packages (~D):~n~n', [Count]),
  123    maplist(pack_info(list), Packages),
  124    validate_dependencies.
  125pack_list_installed :-
  126    print_message(informational, pack(no_packages_installed)).
 pack_info(+Pack)
Print more detailed information about Pack.
  132pack_info(Name) :-
  133    pack_info(info, Name).
  134
  135pack_info(Level, Name) :-
  136    must_be(atom, Name),
  137    findall(Info, pack_info(Name, Level, Info), Infos0),
  138    (   Infos0 == []
  139    ->  print_message(warning, pack(no_pack_installed(Name))),
  140        fail
  141    ;   true
  142    ),
  143    update_dependency_db(Name, Infos0),
  144    findall(Def,  pack_default(Level, Infos, Def), Defs),
  145    append(Infos0, Defs, Infos1),
  146    sort(Infos1, Infos),
  147    show_info(Name, Infos, [info(Level)]).
  148
  149
  150show_info(_Name, _Properties, Options) :-
  151    option(silent(true), Options),
  152    !.
  153show_info(Name, Properties, Options) :-
  154    option(info(list), Options),
  155    !,
  156    memberchk(title(Title), Properties),
  157    memberchk(version(Version), Properties),
  158    format('i ~w@~w ~28|- ~w~n', [Name, Version, Title]).
  159show_info(Name, Properties, _) :-
  160    !,
  161    print_property_value('Package'-'~w', [Name]),
  162    findall(Term, pack_level_info(info, Term, _, _), Terms),
  163    maplist(print_property(Properties), Terms).
  164
  165print_property(_, nl) :-
  166    !,
  167    format('~n').
  168print_property(Properties, Term) :-
  169    findall(Term, member(Term, Properties), Terms),
  170    Terms \== [],
  171    !,
  172    pack_level_info(_, Term, LabelFmt, _Def),
  173    (   LabelFmt = Label-FmtElem
  174    ->  true
  175    ;   Label = LabelFmt,
  176        FmtElem = '~w'
  177    ),
  178    multi_valued(Terms, FmtElem, FmtList, Values),
  179    atomic_list_concat(FmtList, ', ', Fmt),
  180    print_property_value(Label-Fmt, Values).
  181print_property(_, _).
  182
  183multi_valued([H], LabelFmt, [LabelFmt], Values) :-
  184    !,
  185    H =.. [_|Values].
  186multi_valued([H|T], LabelFmt, [LabelFmt|LT], Values) :-
  187    H =.. [_|VH],
  188    append(VH, MoreValues, Values),
  189    multi_valued(T, LabelFmt, LT, MoreValues).
  190
  191
  192pvalue_column(24).
  193print_property_value(Prop-Fmt, Values) :-
  194    !,
  195    pvalue_column(C),
  196    atomic_list_concat(['~w:~t~*|', Fmt, '~n'], Format),
  197    format(Format, [Prop,C|Values]).
  198
  199pack_info(Name, Level, Info) :-
  200    '$pack':pack(Name, BaseDir),
  201    (   Info = directory(BaseDir)
  202    ;   pack_info_term(BaseDir, Info)
  203    ),
  204    pack_level_info(Level, Info, _Format, _Default).
  205
  206:- public pack_level_info/4.                    % used by web-server
  207
  208pack_level_info(_,    title(_),         'Title',                   '<no title>').
  209pack_level_info(_,    version(_),       'Installed version',       '<unknown>').
  210pack_level_info(info, directory(_),     'Installed in directory',  -).
  211pack_level_info(info, author(_, _),     'Author'-'~w <~w>',        -).
  212pack_level_info(info, maintainer(_, _), 'Maintainer'-'~w <~w>',    -).
  213pack_level_info(info, packager(_, _),   'Packager'-'~w <~w>',      -).
  214pack_level_info(info, home(_),          'Home page',               -).
  215pack_level_info(info, download(_),      'Download URL',            -).
  216pack_level_info(_,    provides(_),      'Provides',                -).
  217pack_level_info(_,    requires(_),      'Requires',                -).
  218pack_level_info(_,    conflicts(_),     'Conflicts with',          -).
  219pack_level_info(_,    replaces(_),      'Replaces packages',       -).
  220pack_level_info(info, library(_),	'Provided libraries',      -).
  221
  222pack_default(Level, Infos, Def) :-
  223    pack_level_info(Level, ITerm, _Format, Def),
  224    Def \== (-),
  225    \+ memberchk(ITerm, Infos).
 pack_info_term(+PackDir, ?Info) is nondet
True when Info is meta-data for the package PackName.
  231pack_info_term(BaseDir, Info) :-
  232    directory_file_path(BaseDir, 'pack.pl', InfoFile),
  233    catch(
  234        setup_call_cleanup(
  235            open(InfoFile, read, In),
  236            term_in_stream(In, Info),
  237            close(In)),
  238        error(existence_error(source_sink, InfoFile), _),
  239        ( print_message(error, pack(no_meta_data(BaseDir))),
  240          fail
  241        )).
  242pack_info_term(BaseDir, library(Lib)) :-
  243    atom_concat(BaseDir, '/prolog/', LibDir),
  244    atom_concat(LibDir, '*.pl', Pattern),
  245    expand_file_name(Pattern, Files),
  246    maplist(atom_concat(LibDir), Plain, Files),
  247    convlist(base_name, Plain, Libs),
  248    member(Lib, Libs).
  249
  250base_name(File, Base) :-
  251    file_name_extension(Base, pl, File).
  252
  253term_in_stream(In, Term) :-
  254    repeat,
  255        read_term(In, Term0, []),
  256        (   Term0 == end_of_file
  257        ->  !, fail
  258        ;   Term = Term0,
  259            valid_info_term(Term0)
  260        ).
  261
  262valid_info_term(Term) :-
  263    Term =.. [Name|Args],
  264    same_length(Args, Types),
  265    Decl =.. [Name|Types],
  266    (   pack_info_term(Decl)
  267    ->  maplist(valid_info_arg, Types, Args)
  268    ;   print_message(warning, pack(invalid_info(Term))),
  269        fail
  270    ).
  271
  272valid_info_arg(Type, Arg) :-
  273    must_be(Type, Arg).
 pack_info_term(?Term) is nondet
True when Term describes name and arguments of a valid package info term.
  280pack_info_term(name(atom)).                     % Synopsis
  281pack_info_term(title(atom)).
  282pack_info_term(keywords(list(atom))).
  283pack_info_term(description(list(atom))).
  284pack_info_term(version(version)).
  285pack_info_term(author(atom, email_or_url)).     % Persons
  286pack_info_term(maintainer(atom, email_or_url)).
  287pack_info_term(packager(atom, email_or_url)).
  288pack_info_term(home(atom)).                     % Home page
  289pack_info_term(download(atom)).                 % Source
  290pack_info_term(provides(atom)).                 % Dependencies
  291pack_info_term(requires(dependency)).
  292pack_info_term(conflicts(dependency)).          % Conflicts with package
  293pack_info_term(replaces(atom)).                 % Replaces another package
  294pack_info_term(autoload(boolean)).              % Default installation options
  295
  296:- multifile
  297    error:has_type/2.  298
  299error:has_type(version, Version) :-
  300    atom(Version),
  301    version_data(Version, _Data).
  302error:has_type(email_or_url, Address) :-
  303    atom(Address),
  304    (   sub_atom(Address, _, _, _, @)
  305    ->  true
  306    ;   uri_is_global(Address)
  307    ).
  308error:has_type(dependency, Value) :-
  309    is_dependency(Value, _Token, _Version).
  310
  311version_data(Version, version(Data)) :-
  312    atomic_list_concat(Parts, '.', Version),
  313    maplist(atom_number, Parts, Data).
  314
  315is_dependency(Token, Token, *) :-
  316    atom(Token).
  317is_dependency(Term, Token, VersionCmp) :-
  318    Term =.. [Op,Token,Version],
  319    cmp(Op, _),
  320    version_data(Version, _),
  321    VersionCmp =.. [Op,Version].
  322
  323cmp(<,  @<).
  324cmp(=<, @=<).
  325cmp(==, ==).
  326cmp(>=, @>=).
  327cmp(>,  @>).
  328
  329
  330                 /*******************************
  331                 *            SEARCH            *
  332                 *******************************/
 pack_search(+Query) is det
 pack_list(+Query) is det
Query package server and installed packages and display results. Query is matches case-insensitively against the name and title of known and installed packages. For each matching package, a single line is displayed that provides:

Hint: ?- pack_list(''). lists all packages.

The predicates pack_list/1 and pack_search/1 are synonyms. Both contact the package server at http://www.swi-prolog.org to find available packages.

See also
- pack_list_installed/0 to list installed packages without contacting the server.
  361pack_list(Query) :-
  362    pack_search(Query).
  363
  364pack_search(Query) :-
  365    query_pack_server(search(Query), Result, []),
  366    (   Result == false
  367    ->  (   local_search(Query, Packs),
  368            Packs \== []
  369        ->  forall(member(pack(Pack, Stat, Title, Version, _), Packs),
  370                   format('~w ~w@~w ~28|- ~w~n',
  371                          [Stat, Pack, Version, Title]))
  372        ;   print_message(warning, pack(search_no_matches(Query)))
  373        )
  374    ;   Result = true(Hits),
  375        local_search(Query, Local),
  376        append(Hits, Local, All),
  377        sort(All, Sorted),
  378        list_hits(Sorted)
  379    ).
  380
  381list_hits([]).
  382list_hits([ pack(Pack, i, Title, Version, _),
  383            pack(Pack, p, Title, Version, _)
  384          | More
  385          ]) :-
  386    !,
  387    format('i ~w@~w ~28|- ~w~n', [Pack, Version, Title]),
  388    list_hits(More).
  389list_hits([ pack(Pack, i, Title, VersionI, _),
  390            pack(Pack, p, _,     VersionS, _)
  391          | More
  392          ]) :-
  393    !,
  394    version_data(VersionI, VDI),
  395    version_data(VersionS, VDS),
  396    (   VDI @< VDS
  397    ->  Tag = ('U')
  398    ;   Tag = ('A')
  399    ),
  400    format('~w ~w@~w(~w) ~28|- ~w~n', [Tag, Pack, VersionI, VersionS, Title]),
  401    list_hits(More).
  402list_hits([ pack(Pack, i, Title, VersionI, _)
  403          | More
  404          ]) :-
  405    !,
  406    format('l ~w@~w ~28|- ~w~n', [Pack, VersionI, Title]),
  407    list_hits(More).
  408list_hits([pack(Pack, Stat, Title, Version, _)|More]) :-
  409    format('~w ~w@~w ~28|- ~w~n', [Stat, Pack, Version, Title]),
  410    list_hits(More).
  411
  412
  413local_search(Query, Packs) :-
  414    findall(Pack, matching_installed_pack(Query, Pack), Packs).
  415
  416matching_installed_pack(Query, pack(Pack, i, Title, Version, URL)) :-
  417    current_pack(Pack),
  418    findall(Term,
  419            ( pack_info(Pack, _, Term),
  420              search_info(Term)
  421            ), Info),
  422    (   sub_atom_icasechk(Pack, _, Query)
  423    ->  true
  424    ;   memberchk(title(Title), Info),
  425        sub_atom_icasechk(Title, _, Query)
  426    ),
  427    option(title(Title), Info, '<no title>'),
  428    option(version(Version), Info, '<no version>'),
  429    option(download(URL), Info, '<no download url>').
  430
  431search_info(title(_)).
  432search_info(version(_)).
  433search_info(download(_)).
  434
  435
  436                 /*******************************
  437                 *            INSTALL           *
  438                 *******************************/
 pack_install(+Spec:atom) is det
Install a package. Spec is one of

After resolving the type of package, pack_install/2 is used to do the actual installation.

  456pack_install(Spec) :-
  457    pack_default_options(Spec, Pack, [], Options),
  458    pack_install(Pack, [pack(Pack)|Options]).
 pack_default_options(+Spec, -Pack, +OptionsIn, -Options) is det
Establish the pack name (Pack) and install options from a specification and options (OptionsIn) provided by the user.
  465pack_default_options(_Spec, Pack, OptsIn, Options) :-
  466    option(already_installed(pack(Pack,_Version)), OptsIn),
  467    !,
  468    Options = OptsIn.
  469pack_default_options(_Spec, Pack, OptsIn, Options) :-
  470    option(url(URL), OptsIn),
  471    !,
  472    (   option(git(_), OptsIn)
  473    ->  Options = OptsIn
  474    ;   git_url(URL, Pack)
  475    ->  Options = [git(true)|OptsIn]
  476    ;   Options = OptsIn
  477    ),
  478    (   nonvar(Pack)
  479    ->  true
  480    ;   option(pack(Pack), Options)
  481    ->  true
  482    ;   pack_version_file(Pack, _Version, URL)
  483    ).
  484pack_default_options(Archive, Pack, _, Options) :-      % Install from archive
  485    must_be(atom, Archive),
  486    \+ uri_is_global(Archive),
  487    expand_file_name(Archive, [File]),
  488    exists_file(File),
  489    !,
  490    pack_version_file(Pack, Version, File),
  491    uri_file_name(FileURL, File),
  492    Options = [url(FileURL), version(Version)].
  493pack_default_options(URL, Pack, _, Options) :-
  494    git_url(URL, Pack),
  495    !,
  496    Options = [git(true), url(URL)].
  497pack_default_options(FileURL, Pack, _, Options) :-      % Install from directory
  498    uri_file_name(FileURL, Dir),
  499    exists_directory(Dir),
  500    pack_info_term(Dir, name(Pack)),
  501    !,
  502    (   pack_info_term(Dir, version(Version))
  503    ->  uri_file_name(DirURL, Dir),
  504        Options = [url(DirURL), version(Version)]
  505    ;   throw(error(existence_error(key, version, Dir),_))
  506    ).
  507pack_default_options(URL, Pack, _, Options) :-          % Install from URL
  508    pack_version_file(Pack, Version, URL),
  509    download_url(URL),
  510    !,
  511    available_download_versions(URL, [URLVersion-LatestURL|_]),
  512    Options = [url(LatestURL)|VersionOptions],
  513    version_options(Version, URLVersion, VersionOptions).
  514pack_default_options(Pack, Pack, OptsIn, Options) :-    % Install from name
  515    \+ uri_is_global(Pack),                             % ignore URLs
  516    query_pack_server(locate(Pack), Reply, OptsIn),
  517    (   Reply = true(Results)
  518    ->  pack_select_candidate(Pack, Results, OptsIn, Options)
  519    ;   print_message(warning, pack(no_match(Pack))),
  520        fail
  521    ).
  522
  523version_options(Version, Version, [version(Version)]) :- !.
  524version_options(Version, _, [version(Version)]) :-
  525    Version = version(List),
  526    maplist(integer, List),
  527    !.
  528version_options(_, _, []).
 pack_select_candidate(+Pack, +AvailableVersions, +OptionsIn, -Options)
Select from available packages.
  534pack_select_candidate(Pack, [Version-_|_], Options,
  535                      [already_installed(pack(Pack, Installed))|Options]) :-
  536    current_pack(Pack),
  537    pack_info(Pack, _, version(InstalledAtom)),
  538    atom_version(InstalledAtom, Installed),
  539    Installed @>= Version,
  540    !.
  541pack_select_candidate(Pack, Available, Options, OptsOut) :-
  542    option(url(URL), Options),
  543    memberchk(_Version-URLs, Available),
  544    memberchk(URL, URLs),
  545    !,
  546    (   git_url(URL, Pack)
  547    ->  Extra = [git(true)]
  548    ;   Extra = []
  549    ),
  550    OptsOut = [url(URL), inquiry(true) | Extra].
  551pack_select_candidate(Pack, [Version-[URL]|_], Options,
  552                      [url(URL), git(true), inquiry(true)]) :-
  553    git_url(URL, Pack),
  554    !,
  555    confirm(install_from(Pack, Version, git(URL)), yes, Options).
  556pack_select_candidate(Pack, [Version-[URL]|More], Options,
  557                      [url(URL), inquiry(true)]) :-
  558    (   More == []
  559    ->  !
  560    ;   true
  561    ),
  562    confirm(install_from(Pack, Version, URL), yes, Options),
  563    !.
  564pack_select_candidate(Pack, [Version-URLs|_], Options,
  565                      [url(URL), inquiry(true)|Rest]) :-
  566    maplist(url_menu_item, URLs, Tagged),
  567    append(Tagged, [cancel=cancel], Menu),
  568    Menu = [Default=_|_],
  569    menu(pack(select_install_from(Pack, Version)),
  570         Menu, Default, Choice, Options),
  571    (   Choice == cancel
  572    ->  fail
  573    ;   Choice = git(URL)
  574    ->  Rest = [git(true)]
  575    ;   Choice = URL,
  576        Rest = []
  577    ).
  578
  579url_menu_item(URL, git(URL)=install_from(git(URL))) :-
  580    git_url(URL, _),
  581    !.
  582url_menu_item(URL, URL=install_from(URL)).
 pack_install(+Name, +Options) is det
Install package Name. Processes the options below. Default options as would be used by pack_install/1 are used to complete the provided Options.
url(+URL)
Source for downloading the package
package_directory(+Dir)
Directory into which to install the package
interactive(+Boolean)
Use default answer without asking the user if there is a default action.
silent(+Boolean)
If true (default false), suppress informational progress messages.
upgrade(+Boolean)
If true (default false), upgrade package if it is already installed.
git(+Boolean)
If true (default false unless URL ends with =.git=), assume the URL is a GIT repository.

Non-interactive installation can be established using the option interactive(false). It is adviced to install from a particular trusted URL instead of the plain pack name for unattented operation.

  613pack_install(Spec, Options) :-
  614    pack_default_options(Spec, Pack, Options, DefOptions),
  615    (   option(already_installed(Installed), DefOptions)
  616    ->  print_message(informational, pack(already_installed(Installed)))
  617    ;   merge_options(Options, DefOptions, PackOptions),
  618        update_dependency_db,
  619        pack_install_dir(PackDir, PackOptions),
  620        pack_install(Pack, PackDir, PackOptions)
  621    ).
  622
  623pack_install_dir(PackDir, Options) :-
  624    option(package_directory(PackDir), Options),
  625    !.
  626pack_install_dir(PackDir, _Options) :-          % TBD: global/user?
  627    absolute_file_name(pack(.), PackDir,
  628                       [ file_type(directory),
  629                         access(write),
  630                         file_errors(fail)
  631                       ]),
  632    !.
  633pack_install_dir(PackDir, Options) :-           % TBD: global/user?
  634    pack_create_install_dir(PackDir, Options).
  635
  636pack_create_install_dir(PackDir, Options) :-
  637    findall(Candidate = create_dir(Candidate),
  638            ( absolute_file_name(pack(.), Candidate, [solutions(all)]),
  639              \+ exists_file(Candidate),
  640              \+ exists_directory(Candidate),
  641              file_directory_name(Candidate, Super),
  642              (   exists_directory(Super)
  643              ->  access_file(Super, write)
  644              ;   true
  645              )
  646            ),
  647            Candidates0),
  648    list_to_set(Candidates0, Candidates),   % keep order
  649    pack_create_install_dir(Candidates, PackDir, Options).
  650
  651pack_create_install_dir(Candidates, PackDir, Options) :-
  652    Candidates = [Default=_|_],
  653    !,
  654    append(Candidates, [cancel=cancel], Menu),
  655    menu(pack(create_pack_dir), Menu, Default, Selected, Options),
  656    Selected \== cancel,
  657    (   catch(make_directory_path(Selected), E,
  658              (print_message(warning, E), fail))
  659    ->  PackDir = Selected
  660    ;   delete(Candidates, PackDir=create_dir(PackDir), Remaining),
  661        pack_create_install_dir(Remaining, PackDir, Options)
  662    ).
  663pack_create_install_dir(_, _, _) :-
  664    print_message(error, pack(cannot_create_dir(pack(.)))),
  665    fail.
 pack_install(+Pack, +PackDir, +Options)
Install package Pack into PackDir. Options:
url(URL)
Install from the given URL, URL is either a file://, a git URL or a download URL.
upgrade(Boolean)
If Pack is already installed and Boolean is true, update the package to the latest version. If Boolean is false print an error and fail.
  680pack_install(Name, _, Options) :-
  681    current_pack(Name),
  682    option(upgrade(false), Options, false),
  683    print_message(error, pack(already_installed(Name))),
  684    pack_info(Name),
  685    print_message(information, pack(remove_with(Name))),
  686    !,
  687    fail.
  688pack_install(Name, PackDir, Options) :-
  689    option(url(URL), Options),
  690    uri_file_name(URL, Source),
  691    !,
  692    pack_install_from_local(Source, PackDir, Name, Options).
  693pack_install(Name, PackDir, Options) :-
  694    option(url(URL), Options),
  695    uri_components(URL, Components),
  696    uri_data(scheme, Components, Scheme),
  697    pack_install_from_url(Scheme, URL, PackDir, Name, Options).
 pack_install_from_local(+Source, +PackTopDir, +Name, +Options)
Install a package from a local media.
To be done
- Provide an option to install directories using a link (or file-links).
  706pack_install_from_local(Source, PackTopDir, Name, Options) :-
  707    exists_directory(Source),
  708    !,
  709    directory_file_path(PackTopDir, Name, PackDir),
  710    prepare_pack_dir(PackDir, Options),
  711    copy_directory(Source, PackDir),
  712    pack_post_install(Name, PackDir, Options).
  713pack_install_from_local(Source, PackTopDir, Name, Options) :-
  714    exists_file(Source),
  715    directory_file_path(PackTopDir, Name, PackDir),
  716    prepare_pack_dir(PackDir, Options),
  717    pack_unpack(Source, PackDir, Name, Options),
  718    pack_post_install(Name, PackDir, Options).
 pack_unpack(+SourceFile, +PackDir, +Pack, +Options)
Unpack an archive to the given package dir.
  725:- if(exists_source(library(archive))).  726pack_unpack(Source, PackDir, Pack, Options) :-
  727    ensure_loaded_archive,
  728    pack_archive_info(Source, Pack, _Info, StripOptions),
  729    prepare_pack_dir(PackDir, Options),
  730    archive_extract(Source, PackDir,
  731                    [ exclude(['._*'])          % MacOS resource forks
  732                    | StripOptions
  733                    ]).
  734:- else.  735pack_unpack(_,_,_,_) :-
  736    existence_error(library, archive).
  737:- endif.  738
  739                 /*******************************
  740                 *             INFO             *
  741                 *******************************/
 pack_archive_info(+Archive, +Pack, -Info, -Strip)
True when Archive archives Pack. Info is unified with the terms from pack.pl in the pack and Strip is the strip-option for archive_extract/3.

Requires library(archive), which is lazily loaded when needed.

Errors
- existence_error(pack_file, 'pack.pl') if the archive doesn't contain pack.pl
- Syntax errors if pack.pl cannot be parsed.
  755:- if(exists_source(library(archive))).  756ensure_loaded_archive :-
  757    current_predicate(archive_open/3),
  758    !.
  759ensure_loaded_archive :-
  760    use_module(library(archive)).
  761
  762pack_archive_info(Archive, Pack, [archive_size(Bytes)|Info], Strip) :-
  763    ensure_loaded_archive,
  764    size_file(Archive, Bytes),
  765    setup_call_cleanup(
  766        archive_open(Archive, Handle, []),
  767        (   repeat,
  768            (   archive_next_header(Handle, InfoFile)
  769            ->  true
  770            ;   !, fail
  771            )
  772        ),
  773        archive_close(Handle)),
  774    file_base_name(InfoFile, 'pack.pl'),
  775    atom_concat(Prefix, 'pack.pl', InfoFile),
  776    strip_option(Prefix, Pack, Strip),
  777    setup_call_cleanup(
  778        archive_open_entry(Handle, Stream),
  779        read_stream_to_terms(Stream, Info),
  780        close(Stream)),
  781    !,
  782    must_be(ground, Info),
  783    maplist(valid_info_term, Info).
  784:- else.  785pack_archive_info(_, _, _, _) :-
  786    existence_error(library, archive).
  787:- endif.  788pack_archive_info(_, _, _, _) :-
  789    existence_error(pack_file, 'pack.pl').
  790
  791strip_option('', _, []) :- !.
  792strip_option('./', _, []) :- !.
  793strip_option(Prefix, Pack, [remove_prefix(Prefix)]) :-
  794    atom_concat(PrefixDir, /, Prefix),
  795    file_base_name(PrefixDir, Base),
  796    (   Base == Pack
  797    ->  true
  798    ;   pack_version_file(Pack, _, Base)
  799    ->  true
  800    ;   \+ sub_atom(PrefixDir, _, _, _, /)
  801    ).
  802
  803read_stream_to_terms(Stream, Terms) :-
  804    read(Stream, Term0),
  805    read_stream_to_terms(Term0, Stream, Terms).
  806
  807read_stream_to_terms(end_of_file, _, []) :- !.
  808read_stream_to_terms(Term0, Stream, [Term0|Terms]) :-
  809    read(Stream, Term1),
  810    read_stream_to_terms(Term1, Stream, Terms).
 pack_git_info(+GitDir, -Hash, -Info) is det
Retrieve info from a cloned git repository that is compatible with pack_archive_info/4.
  818pack_git_info(GitDir, Hash, [git(true), installed_size(Bytes)|Info]) :-
  819    exists_directory(GitDir),
  820    !,
  821    git_ls_tree(Entries, [directory(GitDir)]),
  822    git_hash(Hash, [directory(GitDir)]),
  823    maplist(arg(4), Entries, Sizes),
  824    sum_list(Sizes, Bytes),
  825    directory_file_path(GitDir, 'pack.pl', InfoFile),
  826    read_file_to_terms(InfoFile, Info, [encoding(utf8)]),
  827    must_be(ground, Info),
  828    maplist(valid_info_term, Info).
 download_file_sanity_check(+Archive, +Pack, +Info) is semidet
Perform basic sanity checks on DownloadFile
  834download_file_sanity_check(Archive, Pack, Info) :-
  835    info_field(name(Name), Info),
  836    info_field(version(VersionAtom), Info),
  837    atom_version(VersionAtom, Version),
  838    pack_version_file(PackA, VersionA, Archive),
  839    must_match([Pack, PackA, Name], name),
  840    must_match([Version, VersionA], version).
  841
  842info_field(Field, Info) :-
  843    memberchk(Field, Info),
  844    ground(Field),
  845    !.
  846info_field(Field, _Info) :-
  847    functor(Field, FieldName, _),
  848    print_message(error, pack(missing(FieldName))),
  849    fail.
  850
  851must_match(Values, _Field) :-
  852    sort(Values, [_]),
  853    !.
  854must_match(Values, Field) :-
  855    print_message(error, pack(conflict(Field, Values))),
  856    fail.
  857
  858
  859                 /*******************************
  860                 *         INSTALLATION         *
  861                 *******************************/
 prepare_pack_dir(+Dir, +Options)
Prepare for installing the package into Dir. This should create Dir if it does not exist and warn if the directory already exists, asking to make it empty.
  869prepare_pack_dir(Dir, Options) :-
  870    exists_directory(Dir),
  871    !,
  872    (   empty_directory(Dir)
  873    ->  true
  874    ;   option(upgrade(true), Options)
  875    ->  delete_directory_contents(Dir)
  876    ;   confirm(remove_existing_pack(Dir), yes, Options),
  877        delete_directory_contents(Dir)
  878    ).
  879prepare_pack_dir(Dir, _) :-
  880    make_directory(Dir).
 empty_directory(+Directory) is semidet
True if Directory is empty (holds no files or sub-directories).
  886empty_directory(Dir) :-
  887    \+ ( directory_files(Dir, Entries),
  888         member(Entry, Entries),
  889         \+ special(Entry)
  890       ).
  891
  892special(.).
  893special(..).
 pack_install_from_url(+Scheme, +URL, +PackDir, +Pack, +Options)
Install a package from a remote source. For git repositories, we simply clone. Archives are downloaded. We currently use the built-in HTTP client. For complete coverage, we should consider using an external (e.g., curl) if available.
  903pack_install_from_url(_, URL, PackTopDir, Pack, Options) :-
  904    option(git(true), Options),
  905    !,
  906    directory_file_path(PackTopDir, Pack, PackDir),
  907    prepare_pack_dir(PackDir, Options),
  908    run_process(path(git), [clone, URL, PackDir], []),
  909    pack_git_info(PackDir, Hash, Info),
  910    pack_inquiry(URL, git(Hash), Info, Options),
  911    show_info(Pack, Info, Options),
  912    confirm(git_post_install(PackDir, Pack), yes, Options),
  913    pack_post_install(Pack, PackDir, Options).
  914pack_install_from_url(Scheme, URL, PackTopDir, Pack, Options) :-
  915    download_scheme(Scheme),
  916    directory_file_path(PackTopDir, Pack, PackDir),
  917    prepare_pack_dir(PackDir, Options),
  918    pack_download_dir(PackTopDir, DownLoadDir),
  919    download_file(URL, Pack, DownloadBase, Options),
  920    directory_file_path(DownLoadDir, DownloadBase, DownloadFile),
  921    setup_call_cleanup(
  922        http_open(URL, In,
  923                  [ cert_verify_hook(ssl_verify)
  924                  ]),
  925        setup_call_cleanup(
  926            open(DownloadFile, write, Out, [type(binary)]),
  927            copy_stream_data(In, Out),
  928            close(Out)),
  929        close(In)),
  930    pack_archive_info(DownloadFile, Pack, Info, _),
  931    download_file_sanity_check(DownloadFile, Pack, Info),
  932    pack_inquiry(URL, DownloadFile, Info, Options),
  933    show_info(Pack, Info, Options),
  934    confirm(install_downloaded(DownloadFile), yes, Options),
  935    pack_install_from_local(DownloadFile, PackTopDir, Pack, Options).
 download_file(+URL, +Pack, -File, +Options) is det
  939download_file(URL, Pack, File, Options) :-
  940    option(version(Version), Options),
  941    !,
  942    atom_version(VersionA, Version),
  943    file_name_extension(_, Ext, URL),
  944    format(atom(File), '~w-~w.~w', [Pack, VersionA, Ext]).
  945download_file(URL, Pack, File, _) :-
  946    file_base_name(URL,Basename),
  947    no_int_file_name_extension(Tag,Ext,Basename),
  948    tag_version(Tag,Version),
  949    !,
  950    atom_version(VersionA,Version),
  951    format(atom(File0), '~w-~w', [Pack, VersionA]),
  952    file_name_extension(File0, Ext, File).
  953download_file(URL, _, File, _) :-
  954    file_base_name(URL, File).
 pack_url_file(+URL, -File) is det
True if File is a unique id for the referenced pack and version. Normally, that is simply the base name, but GitHub archives destroy this picture. Needed by the pack manager.
  962pack_url_file(URL, FileID) :-
  963    github_release_url(URL, Pack, Version),
  964    !,
  965    download_file(URL, Pack, FileID, [version(Version)]).
  966pack_url_file(URL, FileID) :-
  967    file_base_name(URL, FileID).
  968
  969
  970:- public ssl_verify/5.
 ssl_verify(+SSL, +ProblemCert, +AllCerts, +FirstCert, +Error)
Currently we accept all certificates. We organise our own security using SHA1 signatures, so we do not care about the source of the data.
  978ssl_verify(_SSL,
  979           _ProblemCertificate, _AllCertificates, _FirstCertificate,
  980           _Error).
  981
  982pack_download_dir(PackTopDir, DownLoadDir) :-
  983    directory_file_path(PackTopDir, 'Downloads', DownLoadDir),
  984    (   exists_directory(DownLoadDir)
  985    ->  true
  986    ;   make_directory(DownLoadDir)
  987    ),
  988    (   access_file(DownLoadDir, write)
  989    ->  true
  990    ;   permission_error(write, directory, DownLoadDir)
  991    ).
 download_url(+URL) is det
True if URL looks like a URL we can download from.
  997download_url(URL) :-
  998    atom(URL),
  999    uri_components(URL, Components),
 1000    uri_data(scheme, Components, Scheme),
 1001    download_scheme(Scheme).
 1002
 1003download_scheme(http).
 1004download_scheme(https) :-
 1005    catch(use_module(library(http/http_ssl_plugin)),
 1006          E, (print_message(warning, E), fail)).
 pack_post_install(+Pack, +PackDir, +Options) is det
Process post installation work. Steps:
 1016pack_post_install(Pack, PackDir, Options) :-
 1017    post_install_foreign(Pack, PackDir,
 1018                         [ build_foreign(if_absent)
 1019                         | Options
 1020                         ]),
 1021    post_install_autoload(PackDir, Options),
 1022    '$pack_attach'(PackDir).
 pack_rebuild(+Pack) is det
Rebuilt possible foreign components of Pack.
 1028pack_rebuild(Pack) :-
 1029    '$pack':pack(Pack, BaseDir),
 1030    !,
 1031    catch(pack_make(BaseDir, [distclean], []), E,
 1032          print_message(warning, E)),
 1033    post_install_foreign(Pack, BaseDir, []).
 1034pack_rebuild(Pack) :-
 1035    existence_error(pack, Pack).
 pack_rebuild is det
Rebuild foreign components of all packages.
 1041pack_rebuild :-
 1042    forall(current_pack(Pack),
 1043           ( print_message(informational, pack(rebuild(Pack))),
 1044             pack_rebuild(Pack)
 1045           )).
 post_install_foreign(+Pack, +PackDir, +Options) is det
Install foreign parts of the package.
 1052post_install_foreign(Pack, PackDir, Options) :-
 1053    is_foreign_pack(PackDir),
 1054    !,
 1055    (   option(build_foreign(if_absent), Options),
 1056        foreign_present(PackDir)
 1057    ->  print_message(informational, pack(kept_foreign(Pack)))
 1058    ;   setup_path,
 1059        save_build_environment(PackDir),
 1060        configure_foreign(PackDir, Options),
 1061        make_foreign(PackDir, Options)
 1062    ).
 1063post_install_foreign(_, _, _).
 1064
 1065foreign_present(PackDir) :-
 1066    current_prolog_flag(arch, Arch),
 1067    atomic_list_concat([PackDir, '/lib'], ForeignBaseDir),
 1068    exists_directory(ForeignBaseDir),
 1069    !,
 1070    atomic_list_concat([PackDir, '/lib/', Arch], ForeignDir),
 1071    exists_directory(ForeignDir),
 1072    current_prolog_flag(shared_object_extension, Ext),
 1073    atomic_list_concat([ForeignDir, '/*.', Ext], Pattern),
 1074    expand_file_name(Pattern, Files),
 1075    Files \== [].
 1076
 1077is_foreign_pack(PackDir) :-
 1078    foreign_file(File),
 1079    directory_file_path(PackDir, File, Path),
 1080    exists_file(Path),
 1081    !.
 1082
 1083foreign_file('configure.in').
 1084foreign_file('configure.ac').
 1085foreign_file('configure').
 1086foreign_file('Makefile').
 1087foreign_file('makefile').
 configure_foreign(+PackDir, +Options) is det
Run configure if it exists. If configure.ac or configure.in exists, first run autoheader and autoconf
 1095configure_foreign(PackDir, Options) :-
 1096    make_configure(PackDir, Options),
 1097    directory_file_path(PackDir, configure, Configure),
 1098    exists_file(Configure),
 1099    !,
 1100    build_environment(BuildEnv),
 1101    run_process(path(bash), [Configure],
 1102                [ env(BuildEnv),
 1103                  directory(PackDir)
 1104                ]).
 1105configure_foreign(_, _).
 1106
 1107make_configure(PackDir, _Options) :-
 1108    directory_file_path(PackDir, 'configure', Configure),
 1109    exists_file(Configure),
 1110    !.
 1111make_configure(PackDir, _Options) :-
 1112    autoconf_master(ConfigMaster),
 1113    directory_file_path(PackDir, ConfigMaster, ConfigureIn),
 1114    exists_file(ConfigureIn),
 1115    !,
 1116    run_process(path(autoheader), [], [directory(PackDir)]),
 1117    run_process(path(autoconf),   [], [directory(PackDir)]).
 1118make_configure(_, _).
 1119
 1120autoconf_master('configure.ac').
 1121autoconf_master('configure.in').
 make_foreign(+PackDir, +Options) is det
Generate the foreign executable.
 1128make_foreign(PackDir, Options) :-
 1129    pack_make(PackDir, [all, check, install], Options).
 1130
 1131pack_make(PackDir, Targets, _Options) :-
 1132    directory_file_path(PackDir, 'Makefile', Makefile),
 1133    exists_file(Makefile),
 1134    !,
 1135    build_environment(BuildEnv),
 1136    ProcessOptions = [ directory(PackDir), env(BuildEnv) ],
 1137    forall(member(Target, Targets),
 1138           run_process(path(make), [Target], ProcessOptions)).
 1139pack_make(_, _, _).
 save_build_environment(+PackDir)
Create a shell-script build.env that contains the build environment.
 1146save_build_environment(PackDir) :-
 1147    directory_file_path(PackDir, 'buildenv.sh', EnvFile),
 1148    build_environment(Env),
 1149    setup_call_cleanup(
 1150        open(EnvFile, write, Out),
 1151        write_env_script(Out, Env),
 1152        close(Out)).
 1153
 1154write_env_script(Out, Env) :-
 1155    format(Out,
 1156           '# This file contains the environment that can be used to\n\c
 1157                # build the foreign pack outside Prolog.  This file must\n\c
 1158                # be loaded into a bourne-compatible shell using\n\c
 1159                #\n\c
 1160                #   $ source buildenv.sh\n\n',
 1161           []),
 1162    forall(member(Var=Value, Env),
 1163           format(Out, '~w=\'~w\'\n', [Var, Value])),
 1164    format(Out, '\nexport ', []),
 1165    forall(member(Var=_, Env),
 1166           format(Out, ' ~w', [Var])),
 1167    format(Out, '\n', []).
 1168
 1169build_environment(Env) :-
 1170    findall(Name=Value, environment(Name, Value), UserEnv),
 1171    findall(Name=Value,
 1172            ( def_environment(Name, Value),
 1173              \+ memberchk(Name=_, UserEnv)
 1174            ),
 1175            DefEnv),
 1176    append(UserEnv, DefEnv, Env).
 environment(-Name, -Value) is nondet
Hook to define the environment for building packs. This Multifile hook extends the process environment for building foreign extensions. A value provided by this hook overrules defaults provided by def_environment/2. In addition to changing the environment, this may be used to pass additional values to the environment, as in:
prolog_pack:environment('USER', User) :-
    getenv('USER', User).
Arguments:
Name- is an atom denoting a valid variable name
Value- is either an atom or number representing the value of the variable.
 def_environment(-Name, -Value) is nondet
True if Name=Value must appear in the environment for building foreign extensions.
 1203def_environment('PATH', Value) :-
 1204    getenv('PATH', PATH),
 1205    current_prolog_flag(executable, Exe),
 1206    file_directory_name(Exe, ExeDir),
 1207    prolog_to_os_filename(ExeDir, OsExeDir),
 1208    (   current_prolog_flag(windows, true)
 1209    ->  Sep = (;)
 1210    ;   Sep = (:)
 1211    ),
 1212    atomic_list_concat([OsExeDir, Sep, PATH], Value).
 1213def_environment('SWIPL', Value) :-
 1214    current_prolog_flag(executable, Value).
 1215def_environment('SWIPLVERSION', Value) :-
 1216    current_prolog_flag(version, Value).
 1217def_environment('SWIHOME', Value) :-
 1218    current_prolog_flag(home, Value).
 1219def_environment('SWIARCH', Value) :-
 1220    current_prolog_flag(arch, Value).
 1221def_environment('PACKSODIR', Value) :-
 1222    current_prolog_flag(arch, Arch),
 1223    atom_concat('lib/', Arch, Value).
 1224def_environment('SWISOLIB', Value) :-
 1225    current_prolog_flag(c_libplso, Value).
 1226def_environment('SWILIB', '-lswipl').
 1227def_environment('CC', Value) :-
 1228    (   getenv('CC', Value)
 1229    ->  true
 1230    ;   default_c_compiler(Value)
 1231    ->  true
 1232    ;   current_prolog_flag(c_cc, Value)
 1233    ).
 1234def_environment('LD', Value) :-
 1235    (   getenv('LD', Value)
 1236    ->  true
 1237    ;   current_prolog_flag(c_cc, Value)
 1238    ).
 1239def_environment('CFLAGS', Value) :-
 1240    (   getenv('CFLAGS', SystemFlags)
 1241    ->  Extra = [' ', SystemFlags]
 1242    ;   Extra = []
 1243    ),
 1244    current_prolog_flag(c_cflags, Value0),
 1245    current_prolog_flag(home, Home),
 1246    atomic_list_concat([Value0, ' -I"', Home, '/include"' | Extra], Value).
 1247def_environment('LDSOFLAGS', Value) :-
 1248    (   getenv('LDFLAGS', SystemFlags)
 1249    ->  Extra = [SystemFlags|System]
 1250    ;   Extra = System
 1251    ),
 1252    (   current_prolog_flag(windows, true)
 1253    ->  current_prolog_flag(home, Home),
 1254        atomic_list_concat(['-L"', Home, '/bin"'], SystemLib),
 1255        System = [SystemLib]
 1256    ;   current_prolog_flag(c_libplso, '')
 1257    ->  System = []                 % ELF systems do not need this
 1258    ;   prolog_library_dir(SystemLibDir),
 1259        atomic_list_concat(['-L"',SystemLibDir,'"'], SystemLib),
 1260        System = [SystemLib]
 1261    ),
 1262    current_prolog_flag(c_ldflags, LDFlags),
 1263    atomic_list_concat([LDFlags, '-shared' | Extra], ' ', Value).
 1264def_environment('SOEXT', Value) :-
 1265    current_prolog_flag(shared_object_extension, Value).
 1266def_environment(Pass, Value) :-
 1267    pass_env(Pass),
 1268    getenv(Pass, Value).
 1269
 1270pass_env('TMP').
 1271pass_env('TEMP').
 1272pass_env('USER').
 1273pass_env('HOME').
 1274
 1275:- multifile
 1276    prolog:runtime_config/2. 1277
 1278prolog_library_dir(Dir) :-
 1279    prolog:runtime_config(c_libdir, Dir),
 1280    !.
 1281prolog_library_dir(Dir) :-
 1282    current_prolog_flag(home, Home),
 1283    (   current_prolog_flag(c_libdir, Rel)
 1284    ->  atomic_list_concat([Home, Rel], /, Dir)
 1285    ;   current_prolog_flag(arch, Arch)
 1286    ->  atomic_list_concat([Home, lib, Arch], /, Dir)
 1287    ).
 default_c_compiler(-CC) is semidet
Try to find a suitable C compiler for compiling packages with foreign code.
To be done
- Needs proper defaults for Windows. Find MinGW? Find MSVC?
 1296default_c_compiler(CC) :-
 1297    preferred_c_compiler(CC),
 1298    has_program(path(CC), _),
 1299    !.
 1300
 1301preferred_c_compiler(gcc).
 1302preferred_c_compiler(clang).
 1303preferred_c_compiler(cc).
 1304
 1305
 1306                 /*******************************
 1307                 *             PATHS            *
 1308                 *******************************/
 1309
 1310setup_path :-
 1311    has_program(path(make), _),
 1312    has_program(path(gcc), _),
 1313    !.
 1314setup_path :-
 1315    current_prolog_flag(windows, true),
 1316    !,
 1317    (   mingw_extend_path
 1318    ->  true
 1319    ;   print_message(error, pack(no_mingw))
 1320    ).
 1321setup_path.
 1322
 1323has_program(Program, Path) :-
 1324    exe_options(ExeOptions),
 1325    absolute_file_name(Program, Path,
 1326                       [ file_errors(fail)
 1327                       | ExeOptions
 1328                       ]).
 1329
 1330exe_options(Options) :-
 1331    current_prolog_flag(windows, true),
 1332    !,
 1333    Options = [ extensions(['',exe,com]), access(read) ].
 1334exe_options(Options) :-
 1335    Options = [ access(execute) ].
 1336
 1337mingw_extend_path :-
 1338    mingw_root(MinGW),
 1339    directory_file_path(MinGW, bin, MinGWBinDir),
 1340    atom_concat(MinGW, '/msys/*/bin', Pattern),
 1341    expand_file_name(Pattern, MsysDirs),
 1342    last(MsysDirs, MSysBinDir),
 1343    prolog_to_os_filename(MinGWBinDir, WinDirMinGW),
 1344    prolog_to_os_filename(MSysBinDir, WinDirMSYS),
 1345    getenv('PATH', Path0),
 1346    atomic_list_concat([WinDirMSYS, WinDirMinGW, Path0], ';', Path),
 1347    setenv('PATH', Path).
 1348
 1349mingw_root(MinGwRoot) :-
 1350    current_prolog_flag(executable, Exe),
 1351    sub_atom(Exe, 1, _, _, :),
 1352    sub_atom(Exe, 0, 1, _, PlDrive),
 1353    Drives = [PlDrive,c,d],
 1354    member(Drive, Drives),
 1355    format(atom(MinGwRoot), '~a:/MinGW', [Drive]),
 1356    exists_directory(MinGwRoot),
 1357    !.
 1358
 1359
 1360                 /*******************************
 1361                 *           AUTOLOAD           *
 1362                 *******************************/
 post_install_autoload(+PackDir, +Options)
Create an autoload index if the package demands such.
 1368post_install_autoload(PackDir, Options) :-
 1369    option(autoload(true), Options, true),
 1370    pack_info_term(PackDir, autoload(true)),
 1371    !,
 1372    directory_file_path(PackDir, prolog, PrologLibDir),
 1373    make_library_index(PrologLibDir).
 1374post_install_autoload(_, _).
 1375
 1376
 1377                 /*******************************
 1378                 *            UPGRADE           *
 1379                 *******************************/
 pack_upgrade(+Pack) is semidet
Try to upgrade the package Pack.
To be done
- Update dependencies when updating a pack from git?
 1387pack_upgrade(Pack) :-
 1388    pack_info(Pack, _, directory(Dir)),
 1389    directory_file_path(Dir, '.git', GitDir),
 1390    exists_directory(GitDir),
 1391    !,
 1392    print_message(informational, pack(git_fetch(Dir))),
 1393    git([fetch], [ directory(Dir) ]),
 1394    git_describe(V0, [ directory(Dir) ]),
 1395    git_describe(V1, [ directory(Dir), commit('origin/master') ]),
 1396    (   V0 == V1
 1397    ->  print_message(informational, pack(up_to_date(Pack)))
 1398    ;   confirm(upgrade(Pack, V0, V1), yes, []),
 1399        git([merge, 'origin/master'], [ directory(Dir) ]),
 1400        pack_rebuild(Pack)
 1401    ).
 1402pack_upgrade(Pack) :-
 1403    once(pack_info(Pack, _, version(VersionAtom))),
 1404    atom_version(VersionAtom, Version),
 1405    pack_info(Pack, _, download(URL)),
 1406    (   wildcard_pattern(URL)
 1407    ->  true
 1408    ;   github_url(URL, _User, _Repo)
 1409    ),
 1410    !,
 1411    available_download_versions(URL, [Latest-LatestURL|_Versions]),
 1412    (   Latest @> Version
 1413    ->  confirm(upgrade(Pack, Version, Latest), yes, []),
 1414        pack_install(Pack,
 1415                     [ url(LatestURL),
 1416                       upgrade(true),
 1417                       pack(Pack)
 1418                     ])
 1419    ;   print_message(informational, pack(up_to_date(Pack)))
 1420    ).
 1421pack_upgrade(Pack) :-
 1422    print_message(warning, pack(no_upgrade_info(Pack))).
 1423
 1424
 1425                 /*******************************
 1426                 *            REMOVE            *
 1427                 *******************************/
 pack_remove(+Name) is det
Remove the indicated package.
 1433pack_remove(Pack) :-
 1434    update_dependency_db,
 1435    (   setof(Dep, pack_depends_on(Dep, Pack), Deps)
 1436    ->  confirm_remove(Pack, Deps, Delete),
 1437        forall(member(P, Delete), pack_remove_forced(P))
 1438    ;   pack_remove_forced(Pack)
 1439    ).
 1440
 1441pack_remove_forced(Pack) :-
 1442    catch('$pack_detach'(Pack, BaseDir),
 1443          error(existence_error(pack, Pack), _),
 1444          fail),
 1445    !,
 1446    print_message(informational, pack(remove(BaseDir))),
 1447    delete_directory_and_contents(BaseDir).
 1448pack_remove_forced(Pack) :-
 1449    directory_file_path(Pack, 'pack.pl', PackFile),
 1450    absolute_file_name(pack(PackFile), PackPath,
 1451                       [ access(read),
 1452                         file_errors(fail)
 1453                       ]),
 1454    !,
 1455    file_directory_name(PackPath, BaseDir),
 1456    delete_directory_and_contents(BaseDir).
 1457pack_remove_forced(Pack) :-
 1458    print_message(informational, error(existence_error(pack, Pack),_)).
 1459
 1460confirm_remove(Pack, Deps, Delete) :-
 1461    print_message(warning, pack(depends(Pack, Deps))),
 1462    menu(pack(resolve_remove),
 1463         [ [Pack]      = remove_only(Pack),
 1464           [Pack|Deps] = remove_deps(Pack, Deps),
 1465           []          = cancel
 1466         ], [], Delete, []),
 1467    Delete \== [].
 1468
 1469
 1470                 /*******************************
 1471                 *           PROPERTIES         *
 1472                 *******************************/
 pack_property(?Pack, ?Property) is nondet
True when Property is a property of an installed Pack. This interface is intended for programs that wish to interact with the package manager. Defined properties are:
directory(Directory)
Directory into which the package is installed
version(Version)
Installed version
title(Title)
Full title of the package
author(Author)
Registered author
download(URL)
Official download URL
readme(File)
Package README file (if present)
todo(File)
Package TODO file (if present)
 1495pack_property(Pack, Property) :-
 1496    findall(Pack-Property, pack_property_(Pack, Property), List),
 1497    member(Pack-Property, List).            % make det if applicable
 1498
 1499pack_property_(Pack, Property) :-
 1500    pack_info(Pack, _, Property).
 1501pack_property_(Pack, Property) :-
 1502    \+ \+ info_file(Property, _),
 1503    '$pack':pack(Pack, BaseDir),
 1504    access_file(BaseDir, read),
 1505    directory_files(BaseDir, Files),
 1506    member(File, Files),
 1507    info_file(Property, Pattern),
 1508    downcase_atom(File, Pattern),
 1509    directory_file_path(BaseDir, File, InfoFile),
 1510    arg(1, Property, InfoFile).
 1511
 1512info_file(readme(_), 'readme.txt').
 1513info_file(readme(_), 'readme').
 1514info_file(todo(_),   'todo.txt').
 1515info_file(todo(_),   'todo').
 1516
 1517
 1518                 /*******************************
 1519                 *             GIT              *
 1520                 *******************************/
 git_url(+URL, -Pack) is semidet
True if URL describes a git url for Pack
 1526git_url(URL, Pack) :-
 1527    uri_components(URL, Components),
 1528    uri_data(scheme, Components, Scheme),
 1529    uri_data(path, Components, Path),
 1530    (   Scheme == git
 1531    ->  true
 1532    ;   git_download_scheme(Scheme),
 1533        file_name_extension(_, git, Path)
 1534    ),
 1535    file_base_name(Path, PackExt),
 1536    (   file_name_extension(Pack, git, PackExt)
 1537    ->  true
 1538    ;   Pack = PackExt
 1539    ),
 1540    (   safe_pack_name(Pack)
 1541    ->  true
 1542    ;   domain_error(pack_name, Pack)
 1543    ).
 1544
 1545git_download_scheme(http).
 1546git_download_scheme(https).
 safe_pack_name(+Name:atom) is semidet
Verifies that Name is a valid pack name. This avoids trickery with pack file names to make shell commands behave unexpectly.
 1553safe_pack_name(Name) :-
 1554    atom_length(Name, Len),
 1555    Len >= 3,                               % demand at least three length
 1556    atom_codes(Name, Codes),
 1557    maplist(safe_pack_char, Codes),
 1558    !.
 1559
 1560safe_pack_char(C) :- between(0'a, 0'z, C), !.
 1561safe_pack_char(C) :- between(0'A, 0'Z, C), !.
 1562safe_pack_char(C) :- between(0'0, 0'9, C), !.
 1563safe_pack_char(0'_).
 1564
 1565
 1566                 /*******************************
 1567                 *         VERSION LOGIC        *
 1568                 *******************************/
 pack_version_file(-Pack, -Version, +File) is semidet
True if File is the name of a file or URL of a file that contains Pack at Version. File must have an extension and the basename must be of the form <pack>-<n>{.<m>}*. E.g., mypack-1.5.
 1577pack_version_file(Pack, Version, GitHubRelease) :-
 1578    atomic(GitHubRelease),
 1579    github_release_url(GitHubRelease, Pack, Version),
 1580    !.
 1581pack_version_file(Pack, Version, Path) :-
 1582    atomic(Path),
 1583    file_base_name(Path, File),
 1584    no_int_file_name_extension(Base, _Ext, File),
 1585    atom_codes(Base, Codes),
 1586    (   phrase(pack_version(Pack, Version), Codes),
 1587        safe_pack_name(Pack)
 1588    ->  true
 1589    ).
 1590
 1591no_int_file_name_extension(Base, Ext, File) :-
 1592    file_name_extension(Base0, Ext0, File),
 1593    \+ atom_number(Ext0, _),
 1594    !,
 1595    Base = Base0,
 1596    Ext = Ext0.
 1597no_int_file_name_extension(File, '', File).
 github_release_url(+URL, -Pack, -Version) is semidet
True when URL is the URL of a GitHub release. Such releases are accessible as
https:/github.com/<owner>/<pack>/archive/[vV]?<version>.zip'
 1610github_release_url(URL, Pack, Version) :-
 1611    uri_components(URL, Components),
 1612    uri_data(authority, Components, 'github.com'),
 1613    uri_data(scheme, Components, Scheme),
 1614    download_scheme(Scheme),
 1615    uri_data(path, Components, Path),
 1616    atomic_list_concat(['',_Project,Pack,archive,File], /, Path),
 1617    file_name_extension(Tag, Ext, File),
 1618    github_archive_extension(Ext),
 1619    tag_version(Tag, Version),
 1620    !.
 1621
 1622github_archive_extension(tgz).
 1623github_archive_extension(zip).
 1624
 1625tag_version(Tag, Version) :-
 1626    version_tag_prefix(Prefix),
 1627    atom_concat(Prefix, AtomVersion, Tag),
 1628    atom_version(AtomVersion, Version).
 1629
 1630version_tag_prefix(v).
 1631version_tag_prefix('V').
 1632version_tag_prefix('').
 1633
 1634
 1635:- public
 1636    atom_version/2.
 atom_version(?Atom, ?Version)
Translate between atomic version representation and term representation. The term representation is a list of version components as integers and can be compared using @>
 1644atom_version(Atom, version(Parts)) :-
 1645    (   atom(Atom)
 1646    ->  atom_codes(Atom, Codes),
 1647        phrase(version(Parts), Codes)
 1648    ;   atomic_list_concat(Parts, '.', Atom)
 1649    ).
 1650
 1651pack_version(Pack, version(Parts)) -->
 1652    string(Codes), "-",
 1653    version(Parts),
 1654    !,
 1655    { atom_codes(Pack, Codes)
 1656    }.
 1657
 1658version([_|T]) -->
 1659    "*",
 1660    !,
 1661    (   "."
 1662    ->  version(T)
 1663    ;   []
 1664    ).
 1665version([H|T]) -->
 1666    integer(H),
 1667    (   "."
 1668    ->  version(T)
 1669    ;   { T = [] }
 1670    ).
 1671
 1672integer(H)    --> digit(D0), digits(L), { number_codes(H, [D0|L]) }.
 1673digit(D)      --> [D], { code_type(D, digit) }.
 1674digits([H|T]) --> digit(H), !, digits(T).
 1675digits([])    --> [].
 1676
 1677
 1678                 /*******************************
 1679                 *       QUERY CENTRAL DB       *
 1680                 *******************************/
 pack_inquiry(+URL, +DownloadFile, +Info, +Options) is semidet
Query the status of a package with the central repository. To do this, we POST a Prolog document containing the URL, info and the SHA1 hash to http://www.swi-prolog.org/pack/eval. The server replies using a list of Prolog terms, described below. The only member that is always included is downloads (with default value 0).
alt_hash(Count, URLs, Hash)
A file with the same base-name, but a different hash was found at URLs and downloaded Count times.
downloads(Count)
Number of times a file with this hash was downloaded.
rating(VoteCount, Rating)
User rating (1..5), provided based on VoteCount votes.
dependency(Token, Pack, Version, URLs, SubDeps)
Required tokens can be provided by the given provides.
 1700pack_inquiry(_, _, _, Options) :-
 1701    option(inquiry(false), Options),
 1702    !.
 1703pack_inquiry(URL, DownloadFile, Info, Options) :-
 1704    setting(server, ServerBase),
 1705    ServerBase \== '',
 1706    atom_concat(ServerBase, query, Server),
 1707    (   option(inquiry(true), Options)
 1708    ->  true
 1709    ;   confirm(inquiry(Server), yes, Options)
 1710    ),
 1711    !,
 1712    (   DownloadFile = git(SHA1)
 1713    ->  true
 1714    ;   file_sha1(DownloadFile, SHA1)
 1715    ),
 1716    query_pack_server(install(URL, SHA1, Info), Reply, Options),
 1717    inquiry_result(Reply, URL, Options).
 1718pack_inquiry(_, _, _, _).
 query_pack_server(+Query, -Result, +Options)
Send a Prolog query to the package server and process its results.
 1726query_pack_server(Query, Result, Options) :-
 1727    setting(server, ServerBase),
 1728    ServerBase \== '',
 1729    atom_concat(ServerBase, query, Server),
 1730    format(codes(Data), '~q.~n', Query),
 1731    info_level(Informational, Options),
 1732    print_message(Informational, pack(contacting_server(Server))),
 1733    setup_call_cleanup(
 1734        http_open(Server, In,
 1735                  [ post(codes(application/'x-prolog', Data)),
 1736                    header(content_type, ContentType)
 1737                  ]),
 1738        read_reply(ContentType, In, Result),
 1739        close(In)),
 1740    message_severity(Result, Level, Informational),
 1741    print_message(Level, pack(server_reply(Result))).
 1742
 1743read_reply(ContentType, In, Result) :-
 1744    sub_atom(ContentType, 0, _, _, 'application/x-prolog'),
 1745    !,
 1746    set_stream(In, encoding(utf8)),
 1747    read(In, Result).
 1748read_reply(ContentType, In, _Result) :-
 1749    read_string(In, 500, String),
 1750    print_message(error, pack(no_prolog_response(ContentType, String))),
 1751    fail.
 1752
 1753info_level(Level, Options) :-
 1754    option(silent(true), Options),
 1755    !,
 1756    Level = silent.
 1757info_level(informational, _).
 1758
 1759message_severity(true(_), Informational, Informational).
 1760message_severity(false, warning, _).
 1761message_severity(exception(_), error, _).
 inquiry_result(+Reply, +File, +Options) is semidet
Analyse the results of the inquiry and decide whether to continue or not.
 1769inquiry_result(Reply, File, Options) :-
 1770    findall(Eval, eval_inquiry(Reply, File, Eval, Options), Evaluation),
 1771    \+ member(cancel, Evaluation),
 1772    select_option(git(_), Options, Options1, _),
 1773    forall(member(install_dependencies(Resolution), Evaluation),
 1774           maplist(install_dependency(Options1), Resolution)).
 1775
 1776eval_inquiry(true(Reply), URL, Eval, _) :-
 1777    include(alt_hash, Reply, Alts),
 1778    Alts \== [],
 1779    print_message(warning, pack(alt_hashes(URL, Alts))),
 1780    (   memberchk(downloads(Count), Reply),
 1781        (   git_url(URL, _)
 1782        ->  Default = yes,
 1783            Eval = with_git_commits_in_same_version
 1784        ;   Default = no,
 1785            Eval = with_alt_hashes
 1786        ),
 1787        confirm(continue_with_alt_hashes(Count, URL), Default, [])
 1788    ->  true
 1789    ;   !,                          % Stop other rules
 1790        Eval = cancel
 1791    ).
 1792eval_inquiry(true(Reply), _, Eval, Options) :-
 1793    include(dependency, Reply, Deps),
 1794    Deps \== [],
 1795    select_dependency_resolution(Deps, Eval, Options),
 1796    (   Eval == cancel
 1797    ->  !
 1798    ;   true
 1799    ).
 1800eval_inquiry(true(Reply), URL, true, Options) :-
 1801    file_base_name(URL, File),
 1802    info_level(Informational, Options),
 1803    print_message(Informational, pack(inquiry_ok(Reply, File))).
 1804eval_inquiry(exception(pack(modified_hash(_SHA1-URL, _SHA2-[URL]))),
 1805             URL, Eval, Options) :-
 1806    (   confirm(continue_with_modified_hash(URL), no, Options)
 1807    ->  Eval = true
 1808    ;   Eval = cancel
 1809    ).
 1810
 1811alt_hash(alt_hash(_,_,_)).
 1812dependency(dependency(_,_,_,_,_)).
 select_dependency_resolution(+Deps, -Eval, +Options)
Select a resolution.
To be done
- Exploit backtracking over resolve_dependencies/2.
 1821select_dependency_resolution(Deps, Eval, Options) :-
 1822    resolve_dependencies(Deps, Resolution),
 1823    exclude(local_dep, Resolution, ToBeDone),
 1824    (   ToBeDone == []
 1825    ->  !, Eval = true
 1826    ;   print_message(warning, pack(install_dependencies(Resolution))),
 1827        (   memberchk(_-unresolved, Resolution)
 1828        ->  Default = cancel
 1829        ;   Default = install_deps
 1830        ),
 1831        menu(pack(resolve_deps),
 1832             [ install_deps    = install_deps,
 1833               install_no_deps = install_no_deps,
 1834               cancel          = cancel
 1835             ], Default, Choice, Options),
 1836        (   Choice == cancel
 1837        ->  !, Eval = cancel
 1838        ;   Choice == install_no_deps
 1839        ->  !, Eval = install_no_deps
 1840        ;   !, Eval = install_dependencies(Resolution)
 1841        )
 1842    ).
 1843
 1844local_dep(_-resolved(_)).
 install_dependency(+Options, +TokenResolution)
Install dependencies for the given resolution.
To be done
- : Query URI to use
 1853install_dependency(Options,
 1854                   _Token-resolve(Pack, VersionAtom, [_URL|_], SubResolve)) :-
 1855    atom_version(VersionAtom, Version),
 1856    current_pack(Pack),
 1857    pack_info(Pack, _, version(InstalledAtom)),
 1858    atom_version(InstalledAtom, Installed),
 1859    Installed == Version,               % already installed
 1860    !,
 1861    maplist(install_dependency(Options), SubResolve).
 1862install_dependency(Options,
 1863                   _Token-resolve(Pack, VersionAtom, [URL|_], SubResolve)) :-
 1864    !,
 1865    atom_version(VersionAtom, Version),
 1866    merge_options([ url(URL),
 1867                    version(Version),
 1868                    interactive(false),
 1869                    inquiry(false),
 1870                    info(list),
 1871                    pack(Pack)
 1872                  ], Options, InstallOptions),
 1873    pack_install(Pack, InstallOptions),
 1874    maplist(install_dependency(Options), SubResolve).
 1875install_dependency(_, _-_).
 1876
 1877
 1878                 /*******************************
 1879                 *        WILDCARD URIs         *
 1880                 *******************************/
 available_download_versions(+URL, -Versions) is det
Deal with wildcard URLs, returning a list of Version-URL pairs, sorted by version.
To be done
- Deal with protocols other than HTTP
 1889available_download_versions(URL, Versions) :-
 1890    wildcard_pattern(URL),
 1891    github_url(URL, User, Repo),
 1892    !,
 1893    findall(Version-VersionURL,
 1894            github_version(User, Repo, Version, VersionURL),
 1895            Versions).
 1896available_download_versions(URL, Versions) :-
 1897    wildcard_pattern(URL),
 1898    !,
 1899    file_directory_name(URL, DirURL0),
 1900    ensure_slash(DirURL0, DirURL),
 1901    print_message(informational, pack(query_versions(DirURL))),
 1902    setup_call_cleanup(
 1903        http_open(DirURL, In, []),
 1904        load_html(stream(In), DOM,
 1905                  [ syntax_errors(quiet)
 1906                  ]),
 1907        close(In)),
 1908    findall(MatchingURL,
 1909            absolute_matching_href(DOM, URL, MatchingURL),
 1910            MatchingURLs),
 1911    (   MatchingURLs == []
 1912    ->  print_message(warning, pack(no_matching_urls(URL)))
 1913    ;   true
 1914    ),
 1915    versioned_urls(MatchingURLs, VersionedURLs),
 1916    keysort(VersionedURLs, SortedVersions),
 1917    reverse(SortedVersions, Versions),
 1918    print_message(informational, pack(found_versions(Versions))).
 1919available_download_versions(URL, [Version-URL]) :-
 1920    (   pack_version_file(_Pack, Version0, URL)
 1921    ->  Version = Version0
 1922    ;   Version = unknown
 1923    ).
 github_url(+URL, -User, -Repo) is semidet
True when URL refers to a github repository.
 1929github_url(URL, User, Repo) :-
 1930    uri_components(URL, uri_components(https,'github.com',Path,_,_)),
 1931    atomic_list_concat(['',User,Repo|_], /, Path).
 github_version(+User, +Repo, -Version, -VersionURI) is nondet
True when Version is a release version and VersionURI is the download location for the zip file.
 1939github_version(User, Repo, Version, VersionURI) :-
 1940    atomic_list_concat(['',repos,User,Repo,tags], /, Path1),
 1941    uri_components(ApiUri, uri_components(https,'api.github.com',Path1,_,_)),
 1942    setup_call_cleanup(
 1943      http_open(ApiUri, In,
 1944                [ request_header('Accept'='application/vnd.github.v3+json')
 1945                ]),
 1946      json_read_dict(In, Dicts),
 1947      close(In)),
 1948    member(Dict, Dicts),
 1949    atom_string(Tag, Dict.name),
 1950    tag_version(Tag, Version),
 1951    atom_string(VersionURI, Dict.zipball_url).
 1952
 1953wildcard_pattern(URL) :- sub_atom(URL, _, _, _, *).
 1954wildcard_pattern(URL) :- sub_atom(URL, _, _, _, ?).
 1955
 1956ensure_slash(Dir, DirS) :-
 1957    (   sub_atom(Dir, _, _, 0, /)
 1958    ->  DirS = Dir
 1959    ;   atom_concat(Dir, /, DirS)
 1960    ).
 1961
 1962absolute_matching_href(DOM, Pattern, Match) :-
 1963    xpath(DOM, //a(@href), HREF),
 1964    uri_normalized(HREF, Pattern, Match),
 1965    wildcard_match(Pattern, Match).
 1966
 1967versioned_urls([], []).
 1968versioned_urls([H|T0], List) :-
 1969    file_base_name(H, File),
 1970    (   pack_version_file(_Pack, Version, File)
 1971    ->  List = [Version-H|T]
 1972    ;   List = T
 1973    ),
 1974    versioned_urls(T0, T).
 1975
 1976
 1977                 /*******************************
 1978                 *          DEPENDENCIES        *
 1979                 *******************************/
 update_dependency_db
Reload dependency declarations between packages.
 1985update_dependency_db :-
 1986    retractall(pack_requires(_,_)),
 1987    retractall(pack_provides_db(_,_)),
 1988    forall(current_pack(Pack),
 1989           (   findall(Info, pack_info(Pack, dependency, Info), Infos),
 1990               update_dependency_db(Pack, Infos)
 1991           )).
 1992
 1993update_dependency_db(Name, Info) :-
 1994    retractall(pack_requires(Name, _)),
 1995    retractall(pack_provides_db(Name, _)),
 1996    maplist(assert_dep(Name), Info).
 1997
 1998assert_dep(Pack, provides(Token)) :-
 1999    !,
 2000    assertz(pack_provides_db(Pack, Token)).
 2001assert_dep(Pack, requires(Token)) :-
 2002    !,
 2003    assertz(pack_requires(Pack, Token)).
 2004assert_dep(_, _).
 validate_dependencies is det
Validate all dependencies, reporting on failures
 2010validate_dependencies :-
 2011    unsatisfied_dependencies(Unsatisfied),
 2012    !,
 2013    print_message(warning, pack(unsatisfied(Unsatisfied))).
 2014validate_dependencies.
 2015
 2016
 2017unsatisfied_dependencies(Unsatisfied) :-
 2018    findall(Req-Pack, pack_requires(Pack, Req), Reqs0),
 2019    keysort(Reqs0, Reqs1),
 2020    group_pairs_by_key(Reqs1, GroupedReqs),
 2021    exclude(satisfied_dependency, GroupedReqs, Unsatisfied),
 2022    Unsatisfied \== [].
 2023
 2024satisfied_dependency(Needed-_By) :-
 2025    pack_provides(_, Needed),
 2026    !.
 2027satisfied_dependency(Needed-_By) :-
 2028    compound(Needed),
 2029    Needed =.. [Op, Pack, ReqVersion],
 2030    (   pack_provides(Pack, Pack)
 2031    ->  pack_info(Pack, _, version(PackVersion)),
 2032        version_data(PackVersion, PackData)
 2033    ;   Pack == prolog
 2034    ->  current_prolog_flag(version_data, swi(Major,Minor,Patch,_)),
 2035        PackData = [Major,Minor,Patch]
 2036    ),
 2037    version_data(ReqVersion, ReqData),
 2038    cmp(Op, Cmp),
 2039    call(Cmp, PackData, ReqData).
 pack_provides(?Package, ?Token) is multi
True if Pack provides Token. A package always provides itself.
 2045pack_provides(Pack, Pack) :-
 2046    current_pack(Pack).
 2047pack_provides(Pack, Token) :-
 2048    pack_provides_db(Pack, Token).
 pack_depends_on(?Pack, ?Dependency) is nondet
True if Pack requires Dependency, direct or indirect.
 2054pack_depends_on(Pack, Dependency) :-
 2055    (   atom(Pack)
 2056    ->  pack_depends_on_fwd(Pack, Dependency, [Pack])
 2057    ;   pack_depends_on_bwd(Pack, Dependency, [Dependency])
 2058    ).
 2059
 2060pack_depends_on_fwd(Pack, Dependency, Visited) :-
 2061    pack_depends_on_1(Pack, Dep1),
 2062    \+ memberchk(Dep1, Visited),
 2063    (   Dependency = Dep1
 2064    ;   pack_depends_on_fwd(Dep1, Dependency, [Dep1|Visited])
 2065    ).
 2066
 2067pack_depends_on_bwd(Pack, Dependency, Visited) :-
 2068    pack_depends_on_1(Dep1, Dependency),
 2069    \+ memberchk(Dep1, Visited),
 2070    (   Pack = Dep1
 2071    ;   pack_depends_on_bwd(Pack, Dep1, [Dep1|Visited])
 2072    ).
 2073
 2074pack_depends_on_1(Pack, Dependency) :-
 2075    atom(Dependency),
 2076    !,
 2077    pack_provides(Dependency, Token),
 2078    pack_requires(Pack, Token).
 2079pack_depends_on_1(Pack, Dependency) :-
 2080    pack_requires(Pack, Token),
 2081    pack_provides(Dependency, Token).
 resolve_dependencies(+Dependencies, -Resolution) is multi
Resolve dependencies as reported by the remote package server.
Arguments:
Dependencies- is a list of dependency(Token, Pack, Version, URLs, SubDeps)
Resolution- is a list of items
  • Token-resolved(Pack)
  • Token-resolve(Pack, Version, URLs, SubResolve)
  • Token-unresolved
To be done
- Watch out for conflicts
- If there are different packs that resolve a token, make an intelligent choice instead of using the first
 2098resolve_dependencies(Dependencies, Resolution) :-
 2099    maplist(dependency_pair, Dependencies, Pairs0),
 2100    keysort(Pairs0, Pairs1),
 2101    group_pairs_by_key(Pairs1, ByToken),
 2102    maplist(resolve_dep, ByToken, Resolution).
 2103
 2104dependency_pair(dependency(Token, Pack, Version, URLs, SubDeps),
 2105                Token-(Pack-pack(Version,URLs, SubDeps))).
 2106
 2107resolve_dep(Token-Pairs, Token-Resolution) :-
 2108    (   resolve_dep2(Token-Pairs, Resolution)
 2109    *-> true
 2110    ;   Resolution = unresolved
 2111    ).
 2112
 2113resolve_dep2(Token-_, resolved(Pack)) :-
 2114    pack_provides(Pack, Token).
 2115resolve_dep2(_-Pairs, resolve(Pack, VersionAtom, URLs, SubResolves)) :-
 2116    keysort(Pairs, Sorted),
 2117    group_pairs_by_key(Sorted, ByPack),
 2118    member(Pack-Versions, ByPack),
 2119    Pack \== (-),
 2120    maplist(version_pack, Versions, VersionData),
 2121    sort(VersionData, ByVersion),
 2122    reverse(ByVersion, ByVersionLatest),
 2123    member(pack(Version,URLs,SubDeps), ByVersionLatest),
 2124    atom_version(VersionAtom, Version),
 2125    include(dependency, SubDeps, Deps),
 2126    resolve_dependencies(Deps, SubResolves).
 2127
 2128version_pack(pack(VersionAtom,URLs,SubDeps),
 2129             pack(Version,URLs,SubDeps)) :-
 2130    atom_version(VersionAtom, Version).
 2131
 2132
 2133                 /*******************************
 2134                 *          RUN PROCESSES       *
 2135                 *******************************/
 run_process(+Executable, +Argv, +Options) is det
Run Executable. Defined options:
directory(+Dir)
Execute in the given directory
output(-Out)
Unify Out with a list of codes representing stdout of the command. Otherwise the output is handed to print_message/2 with level informational.
error(-Error)
As output(Out), but messages are printed at level error.
env(+Environment)
Environment passed to the new process.
 2152run_process(Executable, Argv, Options) :-
 2153    \+ option(output(_), Options),
 2154    \+ option(error(_), Options),
 2155    current_prolog_flag(unix, true),
 2156    current_prolog_flag(threads, true),
 2157    !,
 2158    process_create_options(Options, Extra),
 2159    process_create(Executable, Argv,
 2160                   [ stdout(pipe(Out)),
 2161                     stderr(pipe(Error)),
 2162                     process(PID)
 2163                   | Extra
 2164                   ]),
 2165    thread_create(relay_output([output-Out, error-Error]), Id, []),
 2166    process_wait(PID, Status),
 2167    thread_join(Id, _),
 2168    (   Status == exit(0)
 2169    ->  true
 2170    ;   throw(error(process_error(process(Executable, Argv), Status), _))
 2171    ).
 2172run_process(Executable, Argv, Options) :-
 2173    process_create_options(Options, Extra),
 2174    setup_call_cleanup(
 2175        process_create(Executable, Argv,
 2176                       [ stdout(pipe(Out)),
 2177                         stderr(pipe(Error)),
 2178                         process(PID)
 2179                       | Extra
 2180                       ]),
 2181        (   read_stream_to_codes(Out, OutCodes, []),
 2182            read_stream_to_codes(Error, ErrorCodes, []),
 2183            process_wait(PID, Status)
 2184        ),
 2185        (   close(Out),
 2186            close(Error)
 2187        )),
 2188    print_error(ErrorCodes, Options),
 2189    print_output(OutCodes, Options),
 2190    (   Status == exit(0)
 2191    ->  true
 2192    ;   throw(error(process_error(process(Executable, Argv), Status), _))
 2193    ).
 2194
 2195process_create_options(Options, Extra) :-
 2196    option(directory(Dir), Options, .),
 2197    (   option(env(Env), Options)
 2198    ->  Extra = [cwd(Dir), env(Env)]
 2199    ;   Extra = [cwd(Dir)]
 2200    ).
 2201
 2202relay_output([]) :- !.
 2203relay_output(Output) :-
 2204    pairs_values(Output, Streams),
 2205    wait_for_input(Streams, Ready, infinite),
 2206    relay(Ready, Output, NewOutputs),
 2207    relay_output(NewOutputs).
 2208
 2209relay([], Outputs, Outputs).
 2210relay([H|T], Outputs0, Outputs) :-
 2211    selectchk(Type-H, Outputs0, Outputs1),
 2212    (   at_end_of_stream(H)
 2213    ->  close(H),
 2214        relay(T, Outputs1, Outputs)
 2215    ;   read_pending_codes(H, Codes, []),
 2216        relay(Type, Codes),
 2217        relay(T, Outputs0, Outputs)
 2218    ).
 2219
 2220relay(error,  Codes) :-
 2221    set_prolog_flag(message_context, []),
 2222    print_error(Codes, []).
 2223relay(output, Codes) :-
 2224    print_output(Codes, []).
 2225
 2226print_output(OutCodes, Options) :-
 2227    option(output(Codes), Options),
 2228    !,
 2229    Codes = OutCodes.
 2230print_output(OutCodes, _) :-
 2231    print_message(informational, pack(process_output(OutCodes))).
 2232
 2233print_error(OutCodes, Options) :-
 2234    option(error(Codes), Options),
 2235    !,
 2236    Codes = OutCodes.
 2237print_error(OutCodes, _) :-
 2238    phrase(classify_message(Level), OutCodes, _),
 2239    print_message(Level, pack(process_output(OutCodes))).
 2240
 2241classify_message(error) -->
 2242    string(_), "fatal:",
 2243    !.
 2244classify_message(error) -->
 2245    string(_), "error:",
 2246    !.
 2247classify_message(warning) -->
 2248    string(_), "warning:",
 2249    !.
 2250classify_message(informational) -->
 2251    [].
 2252
 2253string([]) --> [].
 2254string([H|T]) --> [H], string(T).
 2255
 2256
 2257                 /*******************************
 2258                 *        USER INTERACTION      *
 2259                 *******************************/
 2260
 2261:- multifile prolog:message//1.
 menu(Question, +Alternatives, +Default, -Selection, +Options)
 2265menu(_Question, _Alternatives, Default, Selection, Options) :-
 2266    option(interactive(false), Options),
 2267    !,
 2268    Selection = Default.
 2269menu(Question, Alternatives, Default, Selection, _) :-
 2270    length(Alternatives, N),
 2271    between(1, 5, _),
 2272       print_message(query, Question),
 2273       print_menu(Alternatives, Default, 1),
 2274       print_message(query, pack(menu(select))),
 2275       read_selection(N, Choice),
 2276    !,
 2277    (   Choice == default
 2278    ->  Selection = Default
 2279    ;   nth1(Choice, Alternatives, Selection=_)
 2280    ->  true
 2281    ).
 2282
 2283print_menu([], _, _).
 2284print_menu([Value=Label|T], Default, I) :-
 2285    (   Value == Default
 2286    ->  print_message(query, pack(menu(default_item(I, Label))))
 2287    ;   print_message(query, pack(menu(item(I, Label))))
 2288    ),
 2289    I2 is I + 1,
 2290    print_menu(T, Default, I2).
 2291
 2292read_selection(Max, Choice) :-
 2293    get_single_char(Code),
 2294    (   answered_default(Code)
 2295    ->  Choice = default
 2296    ;   code_type(Code, digit(Choice)),
 2297        between(1, Max, Choice)
 2298    ->  true
 2299    ;   print_message(warning, pack(menu(reply(1,Max)))),
 2300        fail
 2301    ).
 confirm(+Question, +Default, +Options) is semidet
Ask for confirmation.
Arguments:
Default- is one of yes, no or none.
 2309confirm(_Question, Default, Options) :-
 2310    Default \== none,
 2311    option(interactive(false), Options, true),
 2312    !,
 2313    Default == yes.
 2314confirm(Question, Default, _) :-
 2315    between(1, 5, _),
 2316       print_message(query, pack(confirm(Question, Default))),
 2317       read_yes_no(YesNo, Default),
 2318    !,
 2319    format(user_error, '~N', []),
 2320    YesNo == yes.
 2321
 2322read_yes_no(YesNo, Default) :-
 2323    get_single_char(Code),
 2324    code_yes_no(Code, Default, YesNo),
 2325    !.
 2326
 2327code_yes_no(0'y, _, yes).
 2328code_yes_no(0'Y, _, yes).
 2329code_yes_no(0'n, _, no).
 2330code_yes_no(0'N, _, no).
 2331code_yes_no(_, none, _) :- !, fail.
 2332code_yes_no(C, Default, Default) :-
 2333    answered_default(C).
 2334
 2335answered_default(0'\r).
 2336answered_default(0'\n).
 2337answered_default(0'\s).
 2338
 2339
 2340                 /*******************************
 2341                 *            MESSAGES          *
 2342                 *******************************/
 2343
 2344:- multifile prolog:message//1. 2345
 2346prolog:message(pack(Message)) -->
 2347    message(Message).
 2348
 2349:- discontiguous
 2350    message//1,
 2351    label//1. 2352
 2353message(invalid_info(Term)) -->
 2354    [ 'Invalid package description: ~q'-[Term] ].
 2355message(directory_exists(Dir)) -->
 2356    [ 'Package target directory exists and is not empty:', nl,
 2357      '\t~q'-[Dir]
 2358    ].
 2359message(already_installed(pack(Pack, Version))) -->
 2360    { atom_version(AVersion, Version) },
 2361    [ 'Pack `~w'' is already installed @~w'-[Pack, AVersion] ].
 2362message(already_installed(Pack)) -->
 2363    [ 'Pack `~w'' is already installed. Package info:'-[Pack] ].
 2364message(invalid_name(File)) -->
 2365    [ '~w: A package archive must be named <pack>-<version>.<ext>'-[File] ],
 2366    no_tar_gz(File).
 2367
 2368no_tar_gz(File) -->
 2369    { sub_atom(File, _, _, 0, '.tar.gz') },
 2370    !,
 2371    [ nl,
 2372      'Package archive files must have a single extension.  E.g., \'.tgz\''-[]
 2373    ].
 2374no_tar_gz(_) --> [].
 2375
 2376message(kept_foreign(Pack)) -->
 2377    [ 'Found foreign libraries for target platform.'-[], nl,
 2378      'Use ?- pack_rebuild(~q). to rebuild from sources'-[Pack]
 2379    ].
 2380message(no_pack_installed(Pack)) -->
 2381    [ 'No pack ~q installed.  Use ?- pack_list(Pattern) to search'-[Pack] ].
 2382message(no_packages_installed) -->
 2383    { setting(server, ServerBase) },
 2384    [ 'There are no extra packages installed.', nl,
 2385      'Please visit ~wlist.'-[ServerBase]
 2386    ].
 2387message(remove_with(Pack)) -->
 2388    [ 'The package can be removed using: ?- ~q.'-[pack_remove(Pack)]
 2389    ].
 2390message(unsatisfied(Packs)) -->
 2391    [ 'The following dependencies are not satisfied:', nl ],
 2392    unsatisfied(Packs).
 2393message(depends(Pack, Deps)) -->
 2394    [ 'The following packages depend on `~w\':'-[Pack], nl ],
 2395    pack_list(Deps).
 2396message(remove(PackDir)) -->
 2397    [ 'Removing ~q and contents'-[PackDir] ].
 2398message(remove_existing_pack(PackDir)) -->
 2399    [ 'Remove old installation in ~q'-[PackDir] ].
 2400message(install_from(Pack, Version, git(URL))) -->
 2401    [ 'Install ~w@~w from GIT at ~w'-[Pack, Version, URL] ].
 2402message(install_from(Pack, Version, URL)) -->
 2403    [ 'Install ~w@~w from ~w'-[Pack, Version, URL] ].
 2404message(select_install_from(Pack, Version)) -->
 2405    [ 'Select download location for ~w@~w'-[Pack, Version] ].
 2406message(install_downloaded(File)) -->
 2407    { file_base_name(File, Base),
 2408      size_file(File, Size) },
 2409    [ 'Install "~w" (~D bytes)'-[Base, Size] ].
 2410message(git_post_install(PackDir, Pack)) -->
 2411    (   { is_foreign_pack(PackDir) }
 2412    ->  [ 'Run post installation scripts for pack "~w"'-[Pack] ]
 2413    ;   [ 'Activate pack "~w"'-[Pack] ]
 2414    ).
 2415message(no_meta_data(BaseDir)) -->
 2416    [ 'Cannot find pack.pl inside directory ~q.  Not a package?'-[BaseDir] ].
 2417message(inquiry(Server)) -->
 2418    [ 'Verify package status (anonymously)', nl,
 2419      '\tat "~w"'-[Server]
 2420    ].
 2421message(search_no_matches(Name)) -->
 2422    [ 'Search for "~w", returned no matching packages'-[Name] ].
 2423message(rebuild(Pack)) -->
 2424    [ 'Checking pack "~w" for rebuild ...'-[Pack] ].
 2425message(upgrade(Pack, From, To)) -->
 2426    [ 'Upgrade "~w" from '-[Pack] ],
 2427    msg_version(From), [' to '-[]], msg_version(To).
 2428message(up_to_date(Pack)) -->
 2429    [ 'Package "~w" is up-to-date'-[Pack] ].
 2430message(query_versions(URL)) -->
 2431    [ 'Querying "~w" to find new versions ...'-[URL] ].
 2432message(no_matching_urls(URL)) -->
 2433    [ 'Could not find any matching URL: ~q'-[URL] ].
 2434message(found_versions([Latest-_URL|More])) -->
 2435    { length(More, Len),
 2436      atom_version(VLatest, Latest)
 2437    },
 2438    [ '    Latest version: ~w (~D older)'-[VLatest, Len] ].
 2439message(process_output(Codes)) -->
 2440    { split_lines(Codes, Lines) },
 2441    process_lines(Lines).
 2442message(contacting_server(Server)) -->
 2443    [ 'Contacting server at ~w ...'-[Server], flush ].
 2444message(server_reply(true(_))) -->
 2445    [ at_same_line, ' ok'-[] ].
 2446message(server_reply(false)) -->
 2447    [ at_same_line, ' done'-[] ].
 2448message(server_reply(exception(E))) -->
 2449    [ 'Server reported the following error:'-[], nl ],
 2450    '$messages':translate_message(E).
 2451message(cannot_create_dir(Alias)) -->
 2452    { setof(PackDir,
 2453            absolute_file_name(Alias, PackDir, [solutions(all)]),
 2454            PackDirs)
 2455    },
 2456    [ 'Cannot find a place to create a package directory.'-[],
 2457      'Considered:'-[]
 2458    ],
 2459    candidate_dirs(PackDirs).
 2460message(no_match(Name)) -->
 2461    [ 'No registered pack matches "~w"'-[Name] ].
 2462message(conflict(version, [PackV, FileV])) -->
 2463    ['Version mismatch: pack.pl: '-[]], msg_version(PackV),
 2464    [', file claims version '-[]], msg_version(FileV).
 2465message(conflict(name, [PackInfo, FileInfo])) -->
 2466    ['Pack ~w mismatch: pack.pl: ~p'-[PackInfo]],
 2467    [', file claims ~w: ~p'-[FileInfo]].
 2468message(no_prolog_response(ContentType, String)) -->
 2469    [ 'Expected Prolog response.  Got content of type ~p'-[ContentType], nl,
 2470      '~s'-[String]
 2471    ].
 2472message(pack(no_upgrade_info(Pack))) -->
 2473    [ '~w: pack meta-data does not provide an upgradable URL'-[Pack] ].
 2474
 2475candidate_dirs([]) --> [].
 2476candidate_dirs([H|T]) --> [ nl, '    ~w'-[H] ], candidate_dirs(T).
 2477
 2478message(no_mingw) -->
 2479    [ 'Cannot find MinGW and/or MSYS.'-[] ].
 2480
 2481                                                % Questions
 2482message(resolve_remove) -->
 2483    [ nl, 'Please select an action:', nl, nl ].
 2484message(create_pack_dir) -->
 2485    [ nl, 'Create directory for packages', nl ].
 2486message(menu(item(I, Label))) -->
 2487    [ '~t(~d)~6|   '-[I] ],
 2488    label(Label).
 2489message(menu(default_item(I, Label))) -->
 2490    [ '~t(~d)~6| * '-[I] ],
 2491    label(Label).
 2492message(menu(select)) -->
 2493    [ nl, 'Your choice? ', flush ].
 2494message(confirm(Question, Default)) -->
 2495    message(Question),
 2496    confirm_default(Default),
 2497    [ flush ].
 2498message(menu(reply(Min,Max))) -->
 2499    (  { Max =:= Min+1 }
 2500    -> [ 'Please enter ~w or ~w'-[Min,Max] ]
 2501    ;  [ 'Please enter a number between ~w and ~w'-[Min,Max] ]
 2502    ).
 2503
 2504% Alternate hashes for found for the same file
 2505
 2506message(alt_hashes(URL, _Alts)) -->
 2507    { git_url(URL, _)
 2508    },
 2509    !,
 2510    [ 'GIT repository was updated without updating version' ].
 2511message(alt_hashes(URL, Alts)) -->
 2512    { file_base_name(URL, File)
 2513    },
 2514    [ 'Found multiple versions of "~w".'-[File], nl,
 2515      'This could indicate a compromised or corrupted file', nl
 2516    ],
 2517    alt_hashes(Alts).
 2518message(continue_with_alt_hashes(Count, URL)) -->
 2519    [ 'Continue installation from "~w" (downloaded ~D times)'-[URL, Count] ].
 2520message(continue_with_modified_hash(_URL)) -->
 2521    [ 'Pack may be compromised.  Continue anyway'
 2522    ].
 2523message(modified_hash(_SHA1-URL, _SHA2-[URL])) -->
 2524    [ 'Content of ~q has changed.'-[URL]
 2525    ].
 2526
 2527alt_hashes([]) --> [].
 2528alt_hashes([H|T]) --> alt_hash(H), ( {T == []} -> [] ; [nl], alt_hashes(T) ).
 2529
 2530alt_hash(alt_hash(Count, URLs, Hash)) -->
 2531    [ '~t~d~8| ~w'-[Count, Hash] ],
 2532    alt_urls(URLs).
 2533
 2534alt_urls([]) --> [].
 2535alt_urls([H|T]) -->
 2536    [ nl, '    ~w'-[H] ],
 2537    alt_urls(T).
 2538
 2539% Installation dependencies gathered from inquiry server.
 2540
 2541message(install_dependencies(Resolution)) -->
 2542    [ 'Package depends on the following:' ],
 2543    msg_res_tokens(Resolution, 1).
 2544
 2545msg_res_tokens([], _) --> [].
 2546msg_res_tokens([H|T], L) --> msg_res_token(H, L), msg_res_tokens(T, L).
 2547
 2548msg_res_token(Token-unresolved, L) -->
 2549    res_indent(L),
 2550    [ '"~w" cannot be satisfied'-[Token] ].
 2551msg_res_token(Token-resolve(Pack, Version, [URL|_], SubResolves), L) -->
 2552    !,
 2553    res_indent(L),
 2554    [ '"~w", provided by ~w@~w from ~w'-[Token, Pack, Version, URL] ],
 2555    { L2 is L+1 },
 2556    msg_res_tokens(SubResolves, L2).
 2557msg_res_token(Token-resolved(Pack), L) -->
 2558    !,
 2559    res_indent(L),
 2560    [ '"~w", provided by installed pack ~w'-[Token,Pack] ].
 2561
 2562res_indent(L) -->
 2563    { I is L*2 },
 2564    [ nl, '~*c'-[I,0'\s] ].
 2565
 2566message(resolve_deps) -->
 2567    [ nl, 'What do you wish to do' ].
 2568label(install_deps) -->
 2569    [ 'Install proposed dependencies' ].
 2570label(install_no_deps) -->
 2571    [ 'Only install requested package' ].
 2572
 2573
 2574message(git_fetch(Dir)) -->
 2575    [ 'Running "git fetch" in ~q'-[Dir] ].
 2576
 2577% inquiry is blank
 2578
 2579message(inquiry_ok(Reply, File)) -->
 2580    { memberchk(downloads(Count), Reply),
 2581      memberchk(rating(VoteCount, Rating), Reply),
 2582      !,
 2583      length(Stars, Rating),
 2584      maplist(=(0'*), Stars)
 2585    },
 2586    [ '"~w" was downloaded ~D times.  Package rated ~s (~D votes)'-
 2587      [ File, Count, Stars, VoteCount ]
 2588    ].
 2589message(inquiry_ok(Reply, File)) -->
 2590    { memberchk(downloads(Count), Reply)
 2591    },
 2592    [ '"~w" was downloaded ~D times'-[ File, Count ] ].
 2593
 2594                                                % support predicates
 2595unsatisfied([]) --> [].
 2596unsatisfied([Needed-[By]|T]) -->
 2597    [ '  - "~w" is needed by package "~w"'-[Needed, By], nl ],
 2598    unsatisfied(T).
 2599unsatisfied([Needed-By|T]) -->
 2600    [ '  - "~w" is needed by the following packages:'-[Needed], nl ],
 2601    pack_list(By),
 2602    unsatisfied(T).
 2603
 2604pack_list([]) --> [].
 2605pack_list([H|T]) -->
 2606    [ '    - Package "~w"'-[H], nl ],
 2607    pack_list(T).
 2608
 2609process_lines([]) --> [].
 2610process_lines([H|T]) -->
 2611    [ '~s'-[H] ],
 2612    (   {T==[]}
 2613    ->  []
 2614    ;   [nl], process_lines(T)
 2615    ).
 2616
 2617split_lines([], []) :- !.
 2618split_lines(All, [Line1|More]) :-
 2619    append(Line1, [0'\n|Rest], All),
 2620    !,
 2621    split_lines(Rest, More).
 2622split_lines(Line, [Line]).
 2623
 2624label(remove_only(Pack)) -->
 2625    [ 'Only remove package ~w (break dependencies)'-[Pack] ].
 2626label(remove_deps(Pack, Deps)) -->
 2627    { length(Deps, Count) },
 2628    [ 'Remove package ~w and ~D dependencies'-[Pack, Count] ].
 2629label(create_dir(Dir)) -->
 2630    [ '~w'-[Dir] ].
 2631label(install_from(git(URL))) -->
 2632    !,
 2633    [ 'GIT repository at ~w'-[URL] ].
 2634label(install_from(URL)) -->
 2635    [ '~w'-[URL] ].
 2636label(cancel) -->
 2637    [ 'Cancel' ].
 2638
 2639confirm_default(yes) -->
 2640    [ ' Y/n? ' ].
 2641confirm_default(no) -->
 2642    [ ' y/N? ' ].
 2643confirm_default(none) -->
 2644    [ ' y/n? ' ].
 2645
 2646msg_version(Version) -->
 2647    { atom(Version) },
 2648    !,
 2649    [ '~w'-[Version] ].
 2650msg_version(VersionData) -->
 2651    !,
 2652    { atom_version(Atom, VersionData) },
 2653    [ '~w'-[Atom] ]