View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           https://www.swi-prolog.org
    6    Copyright (c)  2012-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_pack,
   38          [ pack_list_installed/0,
   39            pack_info/1,                % +Name
   40            pack_list/1,                % +Keyword
   41            pack_list/2,                % +Query, +Options
   42            pack_search/1,              % +Keyword
   43            pack_install/1,             % +Name
   44            pack_install/2,             % +Name, +Options
   45            pack_install_local/3,       % :Spec, +Dir, +Options
   46            pack_upgrade/1,             % +Name
   47            pack_rebuild/1,             % +Name
   48            pack_rebuild/0,             % All packages
   49            pack_remove/1,              % +Name
   50            pack_remove/2,              % +Name, +Options
   51            pack_publish/2,             % +URL, +Options
   52            pack_property/2             % ?Name, ?Property
   53          ]).   54:- use_module(library(apply)).   55:- use_module(library(error)).   56:- use_module(library(option)).   57:- use_module(library(readutil)).   58:- use_module(library(lists)).   59:- use_module(library(filesex)).   60:- use_module(library(xpath)).   61:- use_module(library(settings)).   62:- use_module(library(uri)).   63:- use_module(library(dcg/basics)).   64:- use_module(library(dcg/high_order)).   65:- use_module(library(http/http_open)).   66:- use_module(library(http/json)).   67:- use_module(library(http/http_client), []).   68:- use_module(library(debug), [assertion/1]).   69:- use_module(library(pairs), [pairs_keys/2]).   70:- autoload(library(git)).   71:- autoload(library(sgml)).   72:- autoload(library(sha)).   73:- autoload(library(build/tools)).   74:- autoload(library(ansi_term), [ansi_format/3]).   75:- autoload(library(pprint), [print_term/2]).   76:- autoload(library(prolog_versions), [require_version/3, cmp_versions/3]).   77:- autoload(library(ugraphs), [vertices_edges_to_ugraph/3, ugraph_layers/2]).   78:- autoload(library(process), [process_which/2]).   79
   80:- meta_predicate
   81    pack_install_local(2, +, +).

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. This library complemented by the built-in predicates such as attach_packs/2 that makes installed packages available as libraries.

The important functionality of this library is encapsulated in the app pack. For help, run

swipl pack help

*/

   96                 /*******************************
   97                 *          CONSTANTS           *
   98                 *******************************/
   99
  100:- setting(server, atom, 'https://www.swi-prolog.org/pack/',
  101           'Server to exchange pack information').  102
  103
  104		 /*******************************
  105		 *       LOCAL DECLARATIONS	*
  106		 *******************************/
  107
  108:- op(900, xfx, @).                     % Token@Version
  109
  110:- meta_predicate det_if(0,0).  111
  112                 /*******************************
  113                 *         PACKAGE INFO         *
  114                 *******************************/
 current_pack(?Pack) is nondet
 current_pack(?Pack, ?Dir) is nondet
True if Pack is a currently installed pack.
  121current_pack(Pack) :-
  122    current_pack(Pack, _).
  123
  124current_pack(Pack, Dir) :-
  125    '$pack':pack(Pack, Dir).
 pack_list_installed is det
List currently installed packages and report possible dependency issues.
  132pack_list_installed :-
  133    pack_list('', [installed(true)]),
  134    validate_dependencies.
 pack_info(+Pack)
Print more detailed information about Pack.
  140pack_info(Name) :-
  141    pack_info(info, Name).
  142
  143pack_info(Level, Name) :-
  144    must_be(atom, Name),
  145    findall(Info, pack_info(Name, Level, Info), Infos0),
  146    (   Infos0 == []
  147    ->  print_message(warning, pack(no_pack_installed(Name))),
  148        fail
  149    ;   true
  150    ),
  151    findall(Def,  pack_default(Level, Infos, Def), Defs),
  152    append(Infos0, Defs, Infos1),
  153    sort(Infos1, Infos),
  154    show_info(Name, Infos, [info(Level)]).
  155
  156
  157show_info(_Name, _Properties, Options) :-
  158    option(silent(true), Options),
  159    !.
  160show_info(_Name, _Properties, Options) :-
  161    option(show_info(false), Options),
  162    !.
  163show_info(Name, Properties, Options) :-
  164    option(info(list), Options),
  165    !,
  166    memberchk(title(Title), Properties),
  167    memberchk(version(Version), Properties),
  168    format('i ~w@~w ~28|- ~w~n', [Name, Version, Title]).
  169show_info(Name, Properties, _) :-
  170    !,
  171    print_property_value('Package'-'~w', [Name]),
  172    findall(Term, pack_level_info(info, Term, _, _), Terms),
  173    maplist(print_property(Properties), Terms).
  174
  175print_property(_, nl) :-
  176    !,
  177    format('~n').
  178print_property(Properties, Term) :-
  179    findall(Term, member(Term, Properties), Terms),
  180    Terms \== [],
  181    !,
  182    pack_level_info(_, Term, LabelFmt, _Def),
  183    (   LabelFmt = Label-FmtElem
  184    ->  true
  185    ;   Label = LabelFmt,
  186        FmtElem = '~w'
  187    ),
  188    multi_valued(Terms, FmtElem, FmtList, Values),
  189    atomic_list_concat(FmtList, ', ', Fmt),
  190    print_property_value(Label-Fmt, Values).
  191print_property(_, _).
  192
  193multi_valued([H], LabelFmt, [LabelFmt], Values) :-
  194    !,
  195    H =.. [_|Values].
  196multi_valued([H|T], LabelFmt, [LabelFmt|LT], Values) :-
  197    H =.. [_|VH],
  198    append(VH, MoreValues, Values),
  199    multi_valued(T, LabelFmt, LT, MoreValues).
  200
  201
  202pvalue_column(29).
  203print_property_value(Prop-Fmt, Values) :-
  204    !,
  205    pvalue_column(C),
  206    atomic_list_concat(['~w:~t~*|', Fmt, '~n'], Format),
  207    format(Format, [Prop,C|Values]).
  208
  209pack_info(Name, Level, Info) :-
  210    '$pack':pack(Name, BaseDir),
  211    pack_dir_info(BaseDir, Level, Info).
  212
  213pack_dir_info(BaseDir, Level, Info) :-
  214    (   Info = directory(BaseDir)
  215    ;   pack_info_term(BaseDir, Info)
  216    ),
  217    pack_level_info(Level, Info, _Format, _Default).
  218
  219:- public pack_level_info/4.                    % used by web-server
  220
  221pack_level_info(_,    title(_),         'Title',                   '<no title>').
  222pack_level_info(_,    version(_),       'Installed version',       '<unknown>').
  223pack_level_info(info, automatic(_),	'Automatic (dependency only)', -).
  224pack_level_info(info, directory(_),     'Installed in directory',  -).
  225pack_level_info(info, link(_),		'Installed as link to'-'~w', -).
  226pack_level_info(info, built(_,_),	'Built on'-'~w for SWI-Prolog ~w', -).
  227pack_level_info(info, author(_, _),     'Author'-'~w <~w>',        -).
  228pack_level_info(info, maintainer(_, _), 'Maintainer'-'~w <~w>',    -).
  229pack_level_info(info, packager(_, _),   'Packager'-'~w <~w>',      -).
  230pack_level_info(info, home(_),          'Home page',               -).
  231pack_level_info(info, download(_),      'Download URL',            -).
  232pack_level_info(_,    provides(_),      'Provides',                -).
  233pack_level_info(_,    requires(_),      'Requires',                -).
  234pack_level_info(_,    conflicts(_),     'Conflicts with',          -).
  235pack_level_info(_,    replaces(_),      'Replaces packages',       -).
  236pack_level_info(info, library(_),	'Provided libraries',      -).
  237
  238pack_default(Level, Infos, Def) :-
  239    pack_level_info(Level, ITerm, _Format, Def),
  240    Def \== (-),
  241    \+ memberchk(ITerm, Infos).
 pack_info_term(+PackDir, ?Info) is nondet
True when Info is meta-data for the package PackName.
  247pack_info_term(BaseDir, Info) :-
  248    directory_file_path(BaseDir, 'pack.pl', InfoFile),
  249    catch(
  250        term_in_file(valid_term(pack_info_term), InfoFile, Info),
  251        error(existence_error(source_sink, InfoFile), _),
  252        ( print_message(error, pack(no_meta_data(BaseDir))),
  253          fail
  254        )).
  255pack_info_term(BaseDir, library(Lib)) :-
  256    atom_concat(BaseDir, '/prolog/', LibDir),
  257    atom_concat(LibDir, '*.pl', Pattern),
  258    expand_file_name(Pattern, Files),
  259    maplist(atom_concat(LibDir), Plain, Files),
  260    convlist(base_name, Plain, Libs),
  261    member(Lib, Libs).
  262pack_info_term(BaseDir, automatic(Boolean)) :-
  263    once(pack_status_dir(BaseDir, automatic(Boolean))).
  264pack_info_term(BaseDir, built(Arch, Prolog)) :-
  265    pack_status_dir(BaseDir, built(Arch, Prolog, _How)).
  266pack_info_term(BaseDir, link(Dest)) :-
  267    read_link(BaseDir, _, Dest).
  268
  269base_name(File, Base) :-
  270    file_name_extension(Base, pl, File).
 term_in_file(:Valid, +File, -Term) is nondet
True when Term appears in file and call(Valid, Term) is true.
  276:- meta_predicate
  277    term_in_file(1, +, -).  278
  279term_in_file(Valid, File, Term) :-
  280    exists_file(File),
  281    setup_call_cleanup(
  282        open(File, read, In, [encoding(utf8)]),
  283        term_in_stream(Valid, In, Term),
  284        close(In)).
  285
  286term_in_stream(Valid, In, Term) :-
  287    repeat,
  288        read_term(In, Term0, []),
  289        (   Term0 == end_of_file
  290        ->  !, fail
  291        ;   Term = Term0,
  292            call(Valid, Term0)
  293        ).
  294
  295:- meta_predicate
  296    valid_term(1,+).  297
  298valid_term(Type, Term) :-
  299    Term =.. [Name|Args],
  300    same_length(Args, Types),
  301    Decl =.. [Name|Types],
  302    (   call(Type, Decl)
  303    ->  maplist(valid_info_arg, Types, Args)
  304    ;   print_message(warning, pack(invalid_term(Type, Term))),
  305        fail
  306    ).
  307
  308valid_info_arg(Type, Arg) :-
  309    must_be(Type, Arg).
 pack_info_term(?Term) is nondet
True when Term describes name and arguments of a valid package info term.
  316pack_info_term(name(atom)).                     % Synopsis
  317pack_info_term(title(atom)).
  318pack_info_term(keywords(list(atom))).
  319pack_info_term(description(list(atom))).
  320pack_info_term(version(version)).
  321pack_info_term(author(atom, email_or_url_or_empty)).     % Persons
  322pack_info_term(maintainer(atom, email_or_url)).
  323pack_info_term(packager(atom, email_or_url)).
  324pack_info_term(pack_version(nonneg)).           % Package convention version
  325pack_info_term(home(atom)).                     % Home page
  326pack_info_term(download(atom)).                 % Source
  327pack_info_term(provides(atom)).                 % Dependencies
  328pack_info_term(requires(dependency)).
  329pack_info_term(conflicts(dependency)).          % Conflicts with package
  330pack_info_term(replaces(atom)).                 % Replaces another package
  331pack_info_term(autoload(boolean)).              % Default installation options
  332
  333:- multifile
  334    error:has_type/2.  335
  336error:has_type(version, Version) :-
  337    atom(Version),
  338    is_version(Version).
  339error:has_type(email_or_url, Address) :-
  340    atom(Address),
  341    (   sub_atom(Address, _, _, _, @)
  342    ->  true
  343    ;   uri_is_global(Address)
  344    ).
  345error:has_type(email_or_url_or_empty, Address) :-
  346    (   Address == ''
  347    ->  true
  348    ;   error:has_type(email_or_url, Address)
  349    ).
  350error:has_type(dependency, Value) :-
  351    is_dependency(Value).
  352
  353is_version(Version) :-
  354    split_string(Version, ".", "", Parts),
  355    maplist(number_string, _, Parts).
  356
  357is_dependency(Var) :-
  358    var(Var),
  359    !,
  360    fail.
  361is_dependency(Token) :-
  362    atom(Token),
  363    !.
  364is_dependency(Term) :-
  365    compound(Term),
  366    compound_name_arguments(Term, Op, [Token,Version]),
  367    atom(Token),
  368    cmp(Op, _),
  369    is_version(Version),
  370    !.
  371is_dependency(PrologToken) :-
  372    is_prolog_token(PrologToken).
  373
  374cmp(<,  @<).
  375cmp(=<, @=<).
  376cmp(==, ==).
  377cmp(>=, @>=).
  378cmp(>,  @>).
  379
  380
  381                 /*******************************
  382                 *            SEARCH            *
  383                 *******************************/
 pack_list(+Query) is det
 pack_list(+Query, +Options) is det
 pack_search(+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:

Options processed:

installed(true)
Only list packages that are locally installed. Contacts the server to compare our local version to the latest available version.
outdated(true)
Only list packages that need to be updated. This option implies installed(true).
server((Server|false))
If false, do not contact the server. This implies installed(true). Otherwise, use the given pack server.

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

The predicates pack_list/1 and pack_search/1 are synonyms. Both contact the package server at https://www.swi-prolog.org to find available packages. Contacting the server can be avoided using the server(false) option.

  425pack_list(Query) :-
  426    pack_list(Query, []).
  427
  428pack_search(Query) :-
  429    pack_list(Query, []).
  430
  431pack_list(Query, Options) :-
  432    (   option(installed(true), Options)
  433    ;   option(outdated(true), Options)
  434    ;   option(server(false), Options)
  435    ),
  436    !,
  437    local_search(Query, Local),
  438    maplist(arg(1), Local, Packs),
  439    (   option(server(false), Options)
  440    ->  Hits = []
  441    ;   query_pack_server(info(Packs), true(Hits), Options)
  442    ),
  443    list_hits(Hits, Local, Options).
  444pack_list(Query, Options) :-
  445    query_pack_server(search(Query), Result, Options),
  446    (   Result == false
  447    ->  (   local_search(Query, Packs),
  448            Packs \== []
  449        ->  forall(member(pack(Pack, Stat, Title, Version, _), Packs),
  450                   format('~w ~w@~w ~28|- ~w~n',
  451                          [Stat, Pack, Version, Title]))
  452        ;   print_message(warning, pack(search_no_matches(Query)))
  453        )
  454    ;   Result = true(Hits), % Hits = list(pack(Name, p, Title, Version, URL))
  455        local_search(Query, Local),
  456        list_hits(Hits, Local, [])
  457    ).
  458
  459list_hits(Hits, Local, Options) :-
  460    append(Hits, Local, All),
  461    sort(All, Sorted),
  462    join_status(Sorted, Packs0),
  463    include(filtered(Options), Packs0, Packs),
  464    maplist(list_hit(Options), Packs).
  465
  466filtered(Options, pack(_,Tag,_,_,_)) :-
  467    option(outdated(true), Options),
  468    !,
  469    Tag == 'U'.
  470filtered(_, _).
  471
  472list_hit(_Options, pack(Pack, Tag, Title, Version, _URL)) =>
  473    list_tag(Tag),
  474    ansi_format(code, '~w', [Pack]),
  475    format('@'),
  476    list_version(Tag, Version),
  477    format('~35|- ', []),
  478    ansi_format(comment, '~w~n', [Title]).
  479
  480list_tag(Tag) :-
  481    tag_color(Tag, Color),
  482    ansi_format(Color, '~w ', [Tag]).
  483
  484list_version(Tag, VersionI-VersionS) =>
  485    tag_color(Tag, Color),
  486    ansi_format(Color, '~w', [VersionI]),
  487    ansi_format(bold, '(~w)', [VersionS]).
  488list_version(_Tag, Version) =>
  489    ansi_format([], '~w', [Version]).
  490
  491tag_color('U', warning) :- !.
  492tag_color('A', comment) :- !.
  493tag_color(_, []).
 join_status(+PacksIn, -PacksOut) is det
Combine local and remote information to assess the status of each package. PacksOut is a list of pack(Name, Status, Version, URL). If the versions do not match, Version is VersionInstalled-VersionRemote and similar for thee URL.
  502join_status([], []).
  503join_status([ pack(Pack, i, Title, Version, URL),
  504              pack(Pack, p, Title, Version, _)
  505            | T0
  506            ],
  507            [ pack(Pack, Tag, Title, Version, URL)
  508            | T
  509            ]) :-
  510    !,
  511    (   pack_status(Pack, automatic(true))
  512    ->  Tag = a
  513    ;   Tag = i
  514    ),
  515    join_status(T0, T).
  516join_status([ pack(Pack, i, Title, VersionI, URLI),
  517              pack(Pack, p, _,     VersionS, URLS)
  518            | T0
  519            ],
  520            [ pack(Pack, Tag, Title, VersionI-VersionS, URLI-URLS)
  521            | T
  522            ]) :-
  523    !,
  524    version_sort_key(VersionI, VDI),
  525    version_sort_key(VersionS, VDS),
  526    (   VDI @< VDS
  527    ->  Tag = 'U'
  528    ;   Tag = 'A'
  529    ),
  530    join_status(T0, T).
  531join_status([ pack(Pack, i, Title, VersionI, URL)
  532            | T0
  533            ],
  534            [ pack(Pack, l, Title, VersionI, URL)
  535            | T
  536            ]) :-
  537    !,
  538    join_status(T0, T).
  539join_status([H|T0], [H|T]) :-
  540    join_status(T0, T).
 local_search(+Query, -Packs:list(atom)) is det
Search locally installed packs.
  546local_search(Query, Packs) :-
  547    findall(Pack, matching_installed_pack(Query, Pack), Packs).
  548
  549matching_installed_pack(Query, pack(Pack, i, Title, Version, URL)) :-
  550    current_pack(Pack),
  551    findall(Term,
  552            ( pack_info(Pack, _, Term),
  553              search_info(Term)
  554            ), Info),
  555    (   sub_atom_icasechk(Pack, _, Query)
  556    ->  true
  557    ;   memberchk(title(Title), Info),
  558        sub_atom_icasechk(Title, _, Query)
  559    ),
  560    option(title(Title), Info, '<no title>'),
  561    option(version(Version), Info, '<no version>'),
  562    option(download(URL), Info, '<no download url>').
  563
  564search_info(title(_)).
  565search_info(version(_)).
  566search_info(download(_)).
  567
  568
  569                 /*******************************
  570                 *            INSTALL           *
  571                 *******************************/
 pack_install(+Spec:atom) is det
 pack_install(+SpecOrList, +Options) is det
Install one or more packs from SpecOrList. SpecOrList is a single specification or a list of specifications. A specification is one of

Processes the options below. Default options as would be used by pack_install/1 are used to complete the provided Options. Note that pack_install/2 can be used through the SWI-Prolog command line app pack as below. Most of the options of this predicate are available as command line options.

swipl pack install <name>

Options:

url(+URL)
Source for downloading the package
pack_directory(+Dir)
Directory into which to install the package.
global(+Boolean)
If true, install in the XDG common application data path, making the pack accessible to everyone. If false, install in the XDG user application data path, making the pack accessible for the current user only. If the option is absent, use the first existing and writable directory. If that doesn't exist find locations where it can be created and prompt the user to do so.
insecure(+Boolean)
When true (default false), do not perform any checks on SSL certificates when downloading using https.
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.
rebuild(Condition)
Rebuild the foreign components. Condition is one of if_absent (default, do nothing if the directory with foreign resources exists), make (run make) or true (run `make distclean` followed by the default configure and build steps).
test(Boolean)
If true (default), run the pack tests.
git(+Boolean)
If true (default false unless URL ends with =.git=), assume the URL is a GIT repository.
link(+Boolean)
Can be used if the installation source is a local directory and the file system supports symbolic links. In this case the system adds the current directory to the pack registration using a symbolic link and performs the local installation steps.
version(+Version)
Demand the pack to satisfy some version requirement. Version is as defined by require_version/3. For example '1.5' is the same as >=('1.5').
branch(+Branch)
When installing from a git repository, clone this branch.
commit(+Commit)
When installing from a git repository, checkout this commit. Commit is either a hash, a tag, a branch or 'HEAD'.
build_type(+Type)
When building using CMake, use -DCMAKE_BUILD_TYPE=Type. Default is the build type of Prolog or Release.
register(+Boolean)
If true (default), register packages as downloaded after performing the download. This contacts the server with the meta-data of each pack that was downloaded. The server will either register the location as a new version or increment the download count. The server stores the IP address of the client. Subsequent downloads of the same version from the same IP address are ignored.
server(+URL)
Pack server to contact. Default is the setting prolog_pack:server, by default set to https://www.swi-prolog.org/pack/

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.

  671pack_install(Spec) :-
  672    pack_default_options(Spec, Pack, [], Options),
  673    pack_install(Pack, [pack(Pack)|Options]).
  674
  675pack_install(Specs, Options) :-
  676    is_list(Specs),
  677    !,
  678    maplist(pack_options(Options), Specs, Pairs),
  679    pack_install_dir(PackTopDir, Options),
  680    pack_install_set(Pairs, PackTopDir, Options).
  681pack_install(Spec, Options) :-
  682    pack_default_options(Spec, Pack, Options, DefOptions),
  683    (   option(already_installed(Installed), DefOptions)
  684    ->  print_message(informational, pack(already_installed(Installed)))
  685    ;   merge_options(Options, DefOptions, PackOptions),
  686        pack_install_dir(PackTopDir, PackOptions),
  687        pack_install_set([Pack-PackOptions], PackTopDir, Options)
  688    ).
  689
  690pack_options(Options, Spec, Pack-PackOptions) :-
  691    pack_default_options(Spec, Pack, Options, DefOptions),
  692    merge_options(Options, DefOptions, PackOptions).
 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. Cases:
  1. Already installed. We must pass that as pack_default_options/4 is called twice from pack_install/2.
  2. Install from a URL due to a url(URL) option. Determine whether the URL is a GIT repository, get the version and pack from the URL.
  3. Install a local archive file. Extract the pack and version from the archive name.
  4. Install from a git URL. Determines the pack, sets git(true) and adds the URL as option.
  5. Install from a directory. Get the info from the packs.pl file.
  6. Install from '.'. Create a symlink to make the current dir accessible as a pack.
  7. Install from a non-git URL Determine pack and version.
  8. Pack name. Query the server to find candidate packs and select an adequate pack.
  718pack_default_options(_Spec, Pack, OptsIn, Options) :-   % (1)
  719    option(already_installed(pack(Pack,_Version)), OptsIn),
  720    !,
  721    Options = OptsIn.
  722pack_default_options(_Spec, Pack, OptsIn, Options) :-   % (2)
  723    option(url(URL), OptsIn),
  724    !,
  725    (   option(git(_), OptsIn)
  726    ->  Options = OptsIn
  727    ;   git_url(URL, Pack)
  728    ->  Options = [git(true)|OptsIn]
  729    ;   Options = OptsIn
  730    ),
  731    (   nonvar(Pack)
  732    ->  true
  733    ;   option(pack(Pack), Options)
  734    ->  true
  735    ;   pack_version_file(Pack, _Version, URL)
  736    ).
  737pack_default_options(Archive, Pack, OptsIn, Options) :- % (3)
  738    must_be(atom, Archive),
  739    \+ uri_is_global(Archive),
  740    expand_file_name(Archive, [File]),
  741    exists_file(File),
  742    !,
  743    (   pack_version_file(Pack, Version, File)
  744    ->  uri_file_name(FileURL, File),
  745        merge_options([url(FileURL), version(Version)], OptsIn, Options)
  746    ;   domain_error(pack_file_name, Archive)
  747    ).
  748pack_default_options(URL, Pack, OptsIn, Options) :-     % (4)
  749    git_url(URL, Pack),
  750    !,
  751    merge_options([git(true), url(URL)], OptsIn, Options).
  752pack_default_options(FileURL, Pack, _, Options) :-      % (5)
  753    uri_file_name(FileURL, Dir),
  754    exists_directory(Dir),
  755    pack_info_term(Dir, name(Pack)),
  756    !,
  757    (   pack_info_term(Dir, version(Version))
  758    ->  uri_file_name(DirURL, Dir),
  759        Options = [url(DirURL), version(Version)]
  760    ;   throw(error(existence_error(key, version, Dir),_))
  761    ).
  762pack_default_options('.', Pack, OptsIn, Options) :-     % (6)
  763    pack_info_term('.', name(Pack)),
  764    !,
  765    working_directory(Dir, Dir),
  766    (   pack_info_term(Dir, version(Version))
  767    ->  uri_file_name(DirURL, Dir),
  768        NewOptions = [url(DirURL), version(Version) | Options1],
  769        (   current_prolog_flag(windows, true)
  770        ->  Options1 = []
  771        ;   Options1 = [link(true), rebuild(make)]
  772        ),
  773        merge_options(NewOptions, OptsIn, Options)
  774    ;   throw(error(existence_error(key, version, Dir),_))
  775    ).
  776pack_default_options(URL, Pack, OptsIn, Options) :-      % (7)
  777    pack_version_file(Pack, Version, URL),
  778    download_url(URL),
  779    !,
  780    available_download_versions(URL, Available),
  781    Available = [URLVersion-LatestURL|_],
  782    NewOptions = [url(LatestURL)|VersionOptions],
  783    version_options(Version, URLVersion, Available, VersionOptions),
  784    merge_options(NewOptions, OptsIn, Options).
  785pack_default_options(Pack, Pack, Options, Options) :-    % (8)
  786    \+ uri_is_global(Pack).
  787
  788version_options(Version, Version, _, [version(Version)]) :- !.
  789version_options(Version, _, Available, [versions(Available)]) :-
  790    sub_atom(Version, _, _, _, *),
  791    !.
  792version_options(_, _, _, []).
 pack_install_dir(-PackDir, +Options) is det
Determine the directory below which to install new packs. This find or creates a writeable directory. Options:

If no writeable directory is found, generate possible location where this directory can be created and ask the user to create one of them.

  812pack_install_dir(PackDir, Options) :-
  813    option(pack_directory(PackDir), Options),
  814    ensure_directory(PackDir),
  815    !.
  816pack_install_dir(PackDir, Options) :-
  817    base_alias(Alias, Options),
  818    absolute_file_name(Alias, PackDir,
  819                       [ file_type(directory),
  820                         access(write),
  821                         file_errors(fail)
  822                       ]),
  823    !.
  824pack_install_dir(PackDir, Options) :-
  825    pack_create_install_dir(PackDir, Options).
  826
  827base_alias(Alias, Options) :-
  828    option(global(true), Options),
  829    !,
  830    Alias = common_app_data(pack).
  831base_alias(Alias, Options) :-
  832    option(global(false), Options),
  833    !,
  834    Alias = user_app_data(pack).
  835base_alias(Alias, _Options) :-
  836    Alias = pack('.').
  837
  838pack_create_install_dir(PackDir, Options) :-
  839    base_alias(Alias, Options),
  840    findall(Candidate = create_dir(Candidate),
  841            ( absolute_file_name(Alias, Candidate, [solutions(all)]),
  842              \+ exists_file(Candidate),
  843              \+ exists_directory(Candidate),
  844              file_directory_name(Candidate, Super),
  845              (   exists_directory(Super)
  846              ->  access_file(Super, write)
  847              ;   true
  848              )
  849            ),
  850            Candidates0),
  851    list_to_set(Candidates0, Candidates),   % keep order
  852    pack_create_install_dir(Candidates, PackDir, Options).
  853
  854pack_create_install_dir(Candidates, PackDir, Options) :-
  855    Candidates = [Default=_|_],
  856    !,
  857    append(Candidates, [cancel=cancel], Menu),
  858    menu(pack(create_pack_dir), Menu, Default, Selected, Options),
  859    Selected \== cancel,
  860    (   catch(make_directory_path(Selected), E,
  861              (print_message(warning, E), fail))
  862    ->  PackDir = Selected
  863    ;   delete(Candidates, PackDir=create_dir(PackDir), Remaining),
  864        pack_create_install_dir(Remaining, PackDir, Options)
  865    ).
  866pack_create_install_dir(_, _, _) :-
  867    print_message(error, pack(cannot_create_dir(pack(.)))),
  868    fail.
 pack_unpack_from_local(+Source, +PackTopDir, +Name, -PackDir, +Options)
Unpack a package from a local media. If Source is a directory, either copy or link the directory. Else, Source must be an archive file. Options:
link(+Boolean)
If the source is a directory, link or copy the directory?
upgrade(true)
If the target is already there, wipe it and make a clean install.
  882pack_unpack_from_local(Source0, PackTopDir, Name, PackDir, Options) :-
  883    exists_directory(Source0),
  884    remove_slash(Source0, Source),
  885    !,
  886    directory_file_path(PackTopDir, Name, PackDir),
  887    (   option(link(true), Options)
  888    ->  (   same_file(Source, PackDir)
  889        ->  true
  890        ;   remove_existing_pack(PackDir, Options),
  891            atom_concat(PackTopDir, '/', PackTopDirS),
  892            relative_file_name(Source, PackTopDirS, RelPath),
  893            link_file(RelPath, PackDir, symbolic),
  894            assertion(same_file(Source, PackDir))
  895        )
  896    ;   is_git_directory(Source)
  897    ->  remove_existing_pack(PackDir, Options),
  898        run_process(path(git), [clone, Source, PackDir], [])
  899    ;   prepare_pack_dir(PackDir, Options),
  900        copy_directory(Source, PackDir)
  901    ).
  902pack_unpack_from_local(Source, PackTopDir, Name, PackDir, Options) :-
  903    exists_file(Source),
  904    directory_file_path(PackTopDir, Name, PackDir),
  905    prepare_pack_dir(PackDir, Options),
  906    pack_unpack(Source, PackDir, Name, Options).
 pack_unpack(+SourceFile, +PackDir, +Pack, +Options)
Unpack an archive to the given package dir.
To be done
- If library(archive) is not provided we could check for a suitable external program such as tar or unzip.
  915:- if(exists_source(library(archive))).  916pack_unpack(Source, PackDir, Pack, Options) :-
  917    ensure_loaded_archive,
  918    pack_archive_info(Source, Pack, _Info, StripOptions),
  919    prepare_pack_dir(PackDir, Options),
  920    archive_extract(Source, PackDir,
  921                    [ exclude(['._*'])          % MacOS resource forks
  922                    | StripOptions
  923                    ]).
  924:- else.  925pack_unpack(_,_,_,_) :-
  926    existence_error(library, archive).
  927:- endif.
 pack_install_local(:Spec, +Dir, +Options) is det
Install a number of packages in a local directory. This predicate supports installing packages local to an application rather than globally.
  935pack_install_local(M:Gen, Dir, Options) :-
  936    findall(Pack-PackOptions, call(M:Gen, Pack, PackOptions), Pairs),
  937    pack_install_set(Pairs, Dir, Options).
  938
  939pack_install_set(Pairs, Dir, Options) :-
  940    must_be(list(pair), Pairs),
  941    ensure_directory(Dir),
  942    partition(known_media, Pairs, Local, Remote),
  943    maplist(pack_options_to_versions, Local, LocalVersions),
  944    (   Remote == []
  945    ->  AllVersions = LocalVersions
  946    ;   pairs_keys(Remote, Packs),
  947        prolog_description(Properties),
  948        query_pack_server(versions(Packs, Properties), Result, Options),
  949        (   Result = true(RemoteVersions)
  950        ->  append(LocalVersions, RemoteVersions, AllVersions)
  951        ;   print_message(error, pack(query_failed(Result))),
  952            fail
  953        )
  954    ),
  955    local_packs(Dir, Existing),
  956    pack_resolve(Pairs, Existing, AllVersions, Plan, Options),
  957    !,                                      % for now, only first plan
  958    Options1 = [pack_directory(Dir)|Options],
  959    download_plan(Pairs, Plan, PlanB, Options1),
  960    register_downloads(PlanB, Options),
  961    maplist(update_automatic, PlanB),
  962    build_plan(PlanB, Built, Options1),
  963    publish_download(PlanB, Options),
  964    work_done(Pairs, Plan, PlanB, Built, Options).
 known_media(+Pair) is semidet
True when the options specify installation from a known media. If that applies to all packs, there is no need to query the server. We first download and unpack the known media, then examine the requirements and, if necessary, go to the server to resolve these.
  973known_media(_-Options) :-
  974    option(url(_), Options).
 pack_resolve(+Pairs, +Existing, +Versions, -Plan, +Options) is det
Generate an installation plan. Pairs is a list of Pack-Options pairs that specifies the desired packages. Existing is a list of pack(Pack, i, Title, Version, URL) terms that represents the already installed packages. Versions is obtained from the server. See pack.pl from the web server for details. On success, this results in a Plan to satisfies the requirements. The plan is a list of packages to install with their location. The steps satisfy the partial ordering of dependencies, such that dependencies are installed before the dependents. Options:
upgrade(true)
When specified, we try to install the latest version of all the packages. Otherwise, we try to minimise the installation.
  992pack_resolve(Pairs, Existing, Versions, Plan, Options) :-
  993    insert_existing(Existing, Versions, AllVersions, Options),
  994    phrase(select_version(Pairs, AllVersions,
  995                          [ plan(PlanA),           % access to plan
  996                            dependency_for([])     % dependencies
  997                          | Options
  998                          ]),
  999           PlanA),
 1000    mark_installed(PlanA, Existing, Plan).
 insert_existing(+Existing, +Available, -Candidates, +Options) is det
Combine the already existing packages with the ones reported as available by the server to a list of Candidates, where the candidate of each package is ordered according by preference. When upgrade(true) is specified, the existing is merged into the set of Available versions. Otherwise Existing is prepended to Available, so it is selected as first.
 1011:- det(insert_existing/4). 1012insert_existing(Existing, [], Versions, _Options) =>
 1013    maplist(existing_to_versions, Existing, Versions).
 1014insert_existing(Existing, [Pack-Versions|T0], AllPackVersions, Options),
 1015    select(Installed, Existing, Existing2),
 1016    Installed.pack == Pack =>
 1017    can_upgrade(Installed, Versions, Installed2),
 1018    insert_existing_(Installed2, Versions, AllVersions, Options),
 1019    AllPackVersions = [Pack-AllVersions|T],
 1020    insert_existing(Existing2, T0, T, Options).
 1021insert_existing(Existing, [H|T0], AllVersions, Options) =>
 1022    AllVersions = [H|T],
 1023    insert_existing(Existing, T0, T, Options).
 1024
 1025existing_to_versions(Installed, Pack-[Version-[Installed]]) :-
 1026    Pack = Installed.pack,
 1027    Version = Installed.version.
 1028
 1029insert_existing_(Installed, Versions, AllVersions, Options) :-
 1030    option(upgrade(true), Options),
 1031    !,
 1032    insert_existing_(Installed, Versions, AllVersions).
 1033insert_existing_(Installed, Versions, AllVersions, _) :-
 1034    AllVersions = [Installed.version-[Installed]|Versions].
 1035
 1036insert_existing_(Installed, [H|T0], [H|T]) :-
 1037    H = V0-_Infos,
 1038    cmp_versions(>, V0, Installed.version),
 1039    !,
 1040    insert_existing_(Installed, T0, T).
 1041insert_existing_(Installed, [H0|T], [H|T]) :-
 1042    H0 = V0-Infos,
 1043    V0 == Installed.version,
 1044    !,
 1045    H = V0-[Installed|Infos].
 1046insert_existing_(Installed, Versions, All) :-
 1047    All =  [Installed.version-[Installed]|Versions].
 can_upgrade(+Installed, +Versions, -Installed2) is det
Add a latest_version key to Installed if its version is older than the latest available version.
 1054can_upgrade(Info, [Version-_|_], Info2) :-
 1055    cmp_versions(>, Version, Info.version),
 1056    !,
 1057    Info2 = Info.put(latest_version, Version).
 1058can_upgrade(Info, _, Info).
 mark_installed(+PlanA, +Existing, -Plan) is det
Mark already up-to-date packs from the plan and add a key upgrade:true to elements of PlanA in Existing that are not the same.
 1066mark_installed([], _, []).
 1067mark_installed([Info|T], Existing, Plan) :-
 1068    (   member(Installed, Existing),
 1069        Installed.pack == Info.pack
 1070    ->  (   (   Installed.git == true
 1071            ->  Info.git == true,
 1072                Installed.hash == Info.hash
 1073            ;   Version = Info.get(version)
 1074            ->  Installed.version == Version
 1075            )
 1076        ->  Plan = [Info.put(keep, true)|PlanT]    % up-to-date
 1077        ;   Plan = [Info.put(upgrade, Installed)|PlanT] % needs upgrade
 1078        )
 1079    ;   Plan = [Info|PlanT]                        % new install
 1080    ),
 1081    mark_installed(T, Existing, PlanT).
 select_version(+PackAndOptions, +Available, +Options)// is nondet
True when the output is a list of pack info dicts that satisfy the installation requirements of PackAndOptions from the packs known to be Available.
 1089select_version([], _, _) -->
 1090    [].
 1091select_version([Pack-PackOptions|More], Versions, Options) -->
 1092    { memberchk(Pack-PackVersions, Versions),
 1093      member(Version-Infos, PackVersions),
 1094      compatible_version(Pack, Version, PackOptions),
 1095      member(Info, Infos),
 1096      pack_options_compatible_with_info(Info, PackOptions),
 1097      pack_satisfies(Pack, Version, Info, Info2, PackOptions),
 1098      all_downloads(PackVersions, Downloads)
 1099    },
 1100    add_to_plan(Info2.put(_{version: Version, all_downloads:Downloads}),
 1101                Versions, Options),
 1102    select_version(More, Versions, Options).
 1103select_version([Pack-_PackOptions|_More], _Versions, _Options) -->
 1104    { existence_error(pack, Pack) }.               % or warn and continue?
 1105
 1106all_downloads(PackVersions, AllDownloads) :-
 1107    aggregate_all(sum(Downloads),
 1108                  ( member(_Version-Infos, PackVersions),
 1109                    member(Info, Infos),
 1110                    get_dict(downloads, Info, Downloads)
 1111                  ),
 1112                  AllDownloads).
 1113
 1114add_requirements([], _, _) -->
 1115    [].
 1116add_requirements([H|T], Versions, Options) -->
 1117    { is_prolog_token(H),
 1118      !,
 1119      prolog_satisfies(H)
 1120    },
 1121    add_requirements(T, Versions, Options).
 1122add_requirements([H|T], Versions, Options) -->
 1123    { member(Pack-PackVersions, Versions),
 1124      member(Version-Infos, PackVersions),
 1125      member(Info, Infos),
 1126      (   Provides = @(Pack,Version)
 1127      ;   member(Provides, Info.get(provides))
 1128      ),
 1129      satisfies_req(Provides, H),
 1130      all_downloads(PackVersions, Downloads)
 1131    },
 1132    add_to_plan(Info.put(_{version: Version, all_downloads:Downloads}),
 1133                Versions, Options),
 1134    add_requirements(T, Versions, Options).
 add_to_plan(+Info, +Versions, +Options) is semidet
Add Info to the plan. If an Info about the same pack is already in the plan, but this is a different version of the pack, we must fail as we cannot install two different versions of a pack.
 1142add_to_plan(Info, _Versions, Options) -->
 1143    { option(plan(Plan), Options),
 1144      member_nonvar(Planned, Plan),
 1145      Planned.pack == Info.pack,
 1146      !,
 1147      same_version(Planned, Info)                  % same pack, different version
 1148    }.
 1149add_to_plan(Info, _Versions, _Options) -->
 1150    { member(Conflict, Info.get(conflicts)),
 1151      is_prolog_token(Conflict),
 1152      prolog_satisfies(Conflict),
 1153      !,
 1154      fail                                         % incompatible with this Prolog
 1155    }.
 1156add_to_plan(Info, _Versions, Options) -->
 1157    { option(plan(Plan), Options),
 1158      member_nonvar(Planned, Plan),
 1159      info_conflicts(Info, Planned),               % Conflicts with a planned pack
 1160      !,
 1161      fail
 1162    }.
 1163add_to_plan(Info, Versions, Options) -->
 1164    { select_option(dependency_for(Dep0), Options, Options1),
 1165      Options2 = [dependency_for([Info.pack|Dep0])|Options1],
 1166      (   Dep0 = [DepFor|_]
 1167      ->  add_dependency_for(DepFor, Info, Info1)
 1168      ;   Info1 = Info
 1169      )
 1170    },
 1171    [Info1],
 1172    add_requirements(Info.get(requires,[]), Versions, Options2).
 1173
 1174add_dependency_for(Pack, Info, Info) :-
 1175    Old = Info.get(dependency_for),
 1176    !,
 1177    b_set_dict(dependency_for, Info, [Pack|Old]).
 1178add_dependency_for(Pack, Info0, Info) :-
 1179    Info = Info0.put(dependency_for, [Pack]).
 1180
 1181same_version(Info, Info) :-
 1182    !.
 1183same_version(Planned, Info) :-
 1184    Hash = Planned.get(hash),
 1185    Hash \== (-),
 1186    !,
 1187    Hash == Info.get(hash).
 1188same_version(Planned, Info) :-
 1189    Planned.get(version) == Info.get(version).
 info_conflicts(+Info1, +Info2) is semidet
True if Info2 is in conflict with Info2. The relation is symetric.
 1195info_conflicts(Info, Planned) :-
 1196    info_conflicts_(Info, Planned),
 1197    !.
 1198info_conflicts(Info, Planned) :-
 1199    info_conflicts_(Planned, Info),
 1200    !.
 1201
 1202info_conflicts_(Info, Planned) :-
 1203    member(Conflict, Info.get(conflicts)),
 1204    \+ is_prolog_token(Conflict),
 1205    info_provides(Planned, Provides),
 1206    satisfies_req(Provides, Conflict),
 1207    !.
 1208
 1209info_provides(Info, Provides) :-
 1210    (   Provides = Info.pack@Info.version
 1211    ;   member(Provides, Info.get(provides))
 1212    ).
 pack_satisfies(+Pack, +Version, +Info0, -Info, +Options) is semidet
True if Pack@Version with Info satisfies the pack installation options provided by Options.
 1219pack_satisfies(_Pack, _Version, Info0, Info, Options) :-
 1220    option(commit('HEAD'), Options),
 1221    !,
 1222    Info0.get(git) == true,
 1223    Info = Info0.put(commit, 'HEAD').
 1224pack_satisfies(_Pack, _Version, Info, Info, Options) :-
 1225    option(commit(Commit), Options),
 1226    !,
 1227    Commit == Info.get(hash).
 1228pack_satisfies(Pack, Version, Info, Info, Options) :-
 1229    option(version(ReqVersion), Options),
 1230    !,
 1231    satisfies_version(Pack, Version, ReqVersion).
 1232pack_satisfies(_Pack, _Version, Info, Info, _Options).
 satisfies_version(+Pack, +PackVersion, +RequiredVersion) is semidet
 1236satisfies_version(Pack, Version, ReqVersion) :-
 1237    catch(require_version(pack(Pack), Version, ReqVersion),
 1238          error(version_error(pack(Pack), Version, ReqVersion),_),
 1239          fail).
 satisfies_req(+Provides, +Required) is semidet
Check a token requirements.
 1245satisfies_req(Token, Token) => true.
 1246satisfies_req(@(Token,_), Token) => true.
 1247satisfies_req(@(Token,PrvVersion), Req), cmp(Req, Token, Cmp, ReqVersion) =>
 1248	cmp_versions(Cmp, PrvVersion, ReqVersion).
 1249satisfies_req(_,_) => fail.
 1250
 1251cmp(Token  < Version, Token, <,	 Version).
 1252cmp(Token =< Version, Token, =<, Version).
 1253cmp(Token =  Version, Token, =,	 Version).
 1254cmp(Token == Version, Token, ==, Version).
 1255cmp(Token >= Version, Token, >=, Version).
 1256cmp(Token >  Version, Token, >,	 Version).
 pack_options_to_versions(+PackOptionsPair, -Versions) is det
Create an available package term from Pack and Options if it contains a url(URL) option. This allows installing packages that are not known to the server. In most cases, the URL will be a git URL or the URL to download an archive. It can also be a file:// url to install from a local archive.

The first clause deals with a wildcard URL. See pack_default_options/4, case (7).

 1269:- det(pack_options_to_versions/2). 1270pack_options_to_versions(Pack-PackOptions, Pack-Versions) :-
 1271    option(versions(Available), PackOptions), !,
 1272    maplist(version_url_info(Pack, PackOptions), Available, Versions).
 1273pack_options_to_versions(Pack-PackOptions, Pack-[Version-[Info]]) :-
 1274    option(url(URL), PackOptions),
 1275    findall(Prop, option_info_prop(PackOptions, Prop), Pairs),
 1276    dict_create(Info, #,
 1277                [ pack-Pack,
 1278                  url-URL
 1279                | Pairs
 1280                ]),
 1281    Version = Info.get(version, '0.0.0').
 1282
 1283version_url_info(Pack, PackOptions, Version-URL, Version-[Info]) :-
 1284    findall(Prop,
 1285            ( option_info_prop(PackOptions, Prop),
 1286              Prop \= version-_
 1287            ),
 1288            Pairs),
 1289    dict_create(Info, #,
 1290                [ pack-Pack,
 1291                  url-URL,
 1292                  version-Version
 1293                | Pairs
 1294                ]).
 1295
 1296option_info_prop(PackOptions, Prop-Value) :-
 1297    option_info(Prop),
 1298    Opt =.. [Prop,Value],
 1299    option(Opt, PackOptions).
 1300
 1301option_info(git).
 1302option_info(hash).
 1303option_info(version).
 1304option_info(branch).
 1305option_info(link).
 compatible_version(+Pack, +Version, +Options) is semidet
Fails if Options demands a version and Version is not compatible with Version.
 1312compatible_version(Pack, Version, PackOptions) :-
 1313    option(version(ReqVersion), PackOptions),
 1314    !,
 1315    satisfies_version(Pack, Version, ReqVersion).
 1316compatible_version(_, _, _).
 pack_options_compatible_with_info(+Info, +PackOptions) is semidet
Ignore information from the server that is incompatible with the request.
 1323pack_options_compatible_with_info(Info, PackOptions) :-
 1324    findall(Prop, option_info_prop(PackOptions, Prop), Pairs),
 1325    dict_create(Dict, _, Pairs),
 1326    Dict >:< Info.
 download_plan(+Targets, +Plan, +Options) is semidet
Download or update all packages from Plan. We need to do this as a first step because we may not have (up-to-date) dependency information about all packs. For example, a pack may be installed at the git HEAD revision that is not yet know to the server or it may be installed from a url that is not known at all at the server.
 1336download_plan(_Targets, Plan, Plan, _Options) :-
 1337    exclude(installed, Plan, []),
 1338    !.
 1339download_plan(Targets, Plan0, Plan, Options) :-
 1340    confirm(download_plan(Plan0), yes, Options),
 1341    maplist(download_from_info(Options), Plan0, Plan1),
 1342    plan_unsatisfied_dependencies(Plan1, Deps),
 1343    (   Deps == []
 1344    ->  Plan = Plan1
 1345    ;   print_message(informational, pack(new_dependencies(Deps))),
 1346        prolog_description(Properties),
 1347        query_pack_server(versions(Deps, Properties), Result, []),
 1348        (   Result = true(Versions)
 1349        ->  pack_resolve(Targets, Plan1, Versions, Plan2, Options),
 1350            !,
 1351            download_plan(Targets, Plan2, Plan, Options)
 1352        ;   print_message(error, pack(query_failed(Result))),
 1353            fail
 1354        )
 1355    ).
 plan_unsatisfied_dependencies(+Plan, -Deps) is det
True when Deps is a list of dependency tokens in Plan that is not satisfied.
 1362plan_unsatisfied_dependencies(Plan, Deps) :-
 1363    phrase(plan_unsatisfied_dependencies(Plan, Plan), Deps).
 1364
 1365plan_unsatisfied_dependencies([], _) -->
 1366    [].
 1367plan_unsatisfied_dependencies([Info|Infos], Plan) -->
 1368    { Deps = Info.get(requires) },
 1369    plan_unsatisfied_requirements(Deps, Plan),
 1370    plan_unsatisfied_dependencies(Infos, Plan).
 1371
 1372plan_unsatisfied_requirements([], _) -->
 1373    [].
 1374plan_unsatisfied_requirements([H|T], Plan) -->
 1375    { is_prolog_token(H),           % Can this fail?
 1376      prolog_satisfies(H)
 1377    },
 1378    !,
 1379    plan_unsatisfied_requirements(T, Plan).
 1380plan_unsatisfied_requirements([H|T], Plan) -->
 1381    { member(Info, Plan),
 1382      (   (   Version = Info.get(version)
 1383          ->  Provides = @(Info.get(pack), Version)
 1384          ;   Provides = Info.get(pack)
 1385          )
 1386      ;   member(Provides, Info.get(provides))
 1387      ),
 1388      satisfies_req(Provides, H)
 1389    }, !,
 1390    plan_unsatisfied_requirements(T, Plan).
 1391plan_unsatisfied_requirements([H|T], Plan) -->
 1392    [H],
 1393    plan_unsatisfied_requirements(T, Plan).
 build_plan(+Plan, -Built, +Options) is det
Run post installation steps. We build dependencies before their dependents, so we first do a topological sort on the packs based on the pack dependencies.
 1402build_plan(Plan, Ordered, Options) :-
 1403    partition(needs_rebuild_from_info(Options), Plan, ToBuild, NoBuild),
 1404    maplist(attach_from_info(Options), NoBuild),
 1405    (   ToBuild == []
 1406    ->  Ordered = []
 1407    ;   order_builds(ToBuild, Ordered),
 1408        confirm(build_plan(Ordered), yes, Options),
 1409        maplist(exec_plan_rebuild_step(Options), Ordered)
 1410    ).
 1411
 1412needs_rebuild_from_info(Options, Info) :-
 1413    needs_rebuild(Info.installed, Options).
 needs_rebuild(+PackDir, +Options) is semidet
True when we need to rebuilt the pack in PackDir.
 1419needs_rebuild(PackDir, Options) :-
 1420    (   is_foreign_pack(PackDir, _),
 1421        \+ is_built(PackDir, Options)
 1422    ->  true
 1423    ;   is_autoload_pack(PackDir, Options),
 1424        post_install_autoload(PackDir, Options),
 1425        fail
 1426    ).
 is_built(+PackDir, +Options) is semidet
True if the pack in PackDir has been built.
To be done
- We now verify it was built by the exact same version. That is normally an overkill.
 1435is_built(PackDir, _Options) :-
 1436    current_prolog_flag(arch, Arch),
 1437    prolog_version_dotted(Version), % Major.Minor.Patch
 1438    pack_status_dir(PackDir, built(Arch, Version, _)).
 order_builds(+ToBuild, -Ordered) is det
Order the build processes by building dependencies before the packages that rely on them as they may need them during the build.
 1445order_builds(ToBuild, Ordered) :-
 1446    findall(Pack-Dep, dep_edge(ToBuild, Pack, Dep), Edges),
 1447    maplist(get_dict(pack), ToBuild, Packs),
 1448    vertices_edges_to_ugraph(Packs, Edges, Graph),
 1449    ugraph_layers(Graph, Layers),
 1450    append(Layers, PackNames),
 1451    maplist(pack_info_from_name(ToBuild), PackNames, Ordered).
 1452
 1453dep_edge(Infos, Pack, Dep) :-
 1454    member(Info, Infos),
 1455    Pack = Info.pack,
 1456    member(Dep, Info.get(dependency_for)),
 1457    (   member(DepInfo, Infos),
 1458        DepInfo.pack == Dep
 1459    ->  true
 1460    ).
 1461
 1462:- det(pack_info_from_name/3). 1463pack_info_from_name(Infos, Pack, Info) :-
 1464    member(Info, Infos),
 1465    Info.pack == Pack,
 1466    !.
 exec_plan_rebuild_step(+Options, +Info) is det
Execute the rebuild steps for the given Info.
 1472exec_plan_rebuild_step(Options, Info) :-
 1473    print_message(informational, pack(build(Info.pack, Info.installed))),
 1474    pack_post_install(Info.pack, Info.installed, Options),
 1475    attach_from_info(Options, Info).
 attach_from_info(+Options, +Info) is det
Make the package visible. Similar to pack_make_available/3.
 1481attach_from_info(_Options, Info) :-
 1482    Info.get(keep) == true,
 1483    !.
 1484attach_from_info(Options, Info) :-
 1485    (   option(pack_directory(_Parent), Options)
 1486    ->  pack_attach(Info.installed, [duplicate(replace)])
 1487    ;   pack_attach(Info.installed, [])
 1488    ).
 download_from_info(+Options, +Info0, -Info) is det
Download a package guided by Info. Note that this does not run any scripts. This implies that dependencies do not matter and we can proceed in any order. This is important because we may use packages at their git HEAD, which implies that requirements may be different from what is in the Info terms.
 1498download_from_info(Options, Info0, Info), option(dryrun(true), Options) =>
 1499    print_term(Info0, [nl(true)]),
 1500    Info = Info0.
 1501download_from_info(_Options, Info0, Info), installed(Info0) =>
 1502    Info = Info0.
 1503download_from_info(_Options, Info0, Info),
 1504    _{upgrade:OldInfo, git:true} :< Info0,
 1505    is_git_directory(OldInfo.installed) =>
 1506    PackDir = OldInfo.installed,
 1507    git_checkout_version(PackDir, [commit(Info0.hash)]),
 1508    reload_info(PackDir, Info0, Info).
 1509download_from_info(Options, Info0, Info),
 1510    _{upgrade:OldInfo} :< Info0 =>
 1511    PackDir = OldInfo.installed,
 1512    detach_pack(OldInfo.pack, PackDir),
 1513    delete_directory_and_contents(PackDir),
 1514    del_dict(upgrade, Info0, _, Info1),
 1515    download_from_info(Options, Info1, Info).
 1516download_from_info(Options, Info0, Info),
 1517    _{url:URL, git:true} :< Info0, \+ have_git =>
 1518    git_archive_url(URL, Archive, Options),
 1519    download_from_info([git_url(URL)|Options],
 1520                       Info0.put(_{ url:Archive,
 1521                                    git:false,
 1522                                    git_url:URL
 1523                                  }),
 1524                       Info1),
 1525                                % restore the hash to register the download.
 1526    (   Info1.get(version) == Info0.get(version),
 1527        Hash = Info0.get(hash)
 1528    ->  Info = Info1.put(hash, Hash)
 1529    ;   Info = Info1
 1530    ).
 1531download_from_info(Options, Info0, Info),
 1532    _{url:URL} :< Info0 =>
 1533    select_option(pack_directory(Dir), Options, Options1),
 1534    select_option(version(_), Options1, Options2, _),
 1535    download_info_extra(Info0, InstallOptions, Options2),
 1536    pack_download_from_url(URL, Dir, Info0.pack,
 1537                           [ interactive(false),
 1538                             pack_dir(PackDir)
 1539                           | InstallOptions
 1540                           ]),
 1541    reload_info(PackDir, Info0, Info).
 1542
 1543download_info_extra(Info, [git(true),commit(Hash)|Options], Options) :-
 1544    Info.get(git) == true,
 1545    !,
 1546    Hash = Info.get(commit, 'HEAD').
 1547download_info_extra(Info, [link(true)|Options], Options) :-
 1548    Info.get(link) == true,
 1549    !.
 1550download_info_extra(_, Options, Options).
 1551
 1552installed(Info) :-
 1553    _ = Info.get(installed).
 1554
 1555detach_pack(Pack, PackDir) :-
 1556    (   current_pack(Pack, PackDir)
 1557    ->  '$pack_detach'(Pack, PackDir)
 1558    ;   true
 1559    ).
 reload_info(+PackDir, +Info0, -Info) is det
Update the requires and provides metadata. Info0 is what we got from the server, but the package may be different as we may have asked for the git HEAD or the package URL may not have been known by the server at all.
 1568reload_info(_PackDir, Info, Info) :-
 1569    _ = Info.get(installed),	% we read it from the package
 1570    !.
 1571reload_info(PackDir, Info0, Info) :-
 1572    local_pack_info(PackDir, Info1),
 1573    Info = Info0.put(installed, PackDir)
 1574                .put(downloaded, Info0.url)
 1575                .put(Info1).
 work_done(+Targets, +Plan, +PlanB, +Built, +Options) is det
Targets has successfully been installed and the packs Built have successfully ran their build scripts.
 1582work_done(_, _, _, _, Options),
 1583    option(silent(true), Options) =>
 1584    true.
 1585work_done(Targets, Plan, Plan, [], _Options) =>
 1586    convlist(can_upgrade_target(Plan), Targets, CanUpgrade),
 1587    (   CanUpgrade == []
 1588    ->  pairs_keys(Targets, Packs),
 1589        print_message(informational, pack(up_to_date(Packs)))
 1590    ;   print_message(informational, pack(installed_can_upgrade(CanUpgrade)))
 1591    ).
 1592work_done(_, _, _, _, _) =>
 1593    true.
 1594
 1595can_upgrade_target(Plan, Pack-_, Info) =>
 1596    member(Info, Plan),
 1597    Info.pack == Pack,
 1598    !,
 1599    _ = Info.get(latest_version).
 local_packs(+Dir, -Packs) is det
True when Packs is a list with information for all installed packages.
 1606local_packs(Dir, Packs) :-
 1607    findall(Pack, pack_in_subdir(Dir, Pack), Packs).
 1608
 1609pack_in_subdir(Dir, Info) :-
 1610    directory_member(Dir, PackDir,
 1611                     [ file_type(directory),
 1612                       hidden(false)
 1613                     ]),
 1614    local_pack_info(PackDir, Info).
 1615
 1616local_pack_info(PackDir,
 1617                #{ pack: Pack,
 1618                   version: Version,
 1619                   title: Title,
 1620                   hash: Hash,
 1621                   url: URL,
 1622                   git: IsGit,
 1623                   requires: Requires,
 1624                   provides: Provides,
 1625                   conflicts: Conflicts,
 1626                   installed: PackDir
 1627                 }) :-
 1628    directory_file_path(PackDir, 'pack.pl', MetaFile),
 1629    exists_file(MetaFile),
 1630    file_base_name(PackDir, DirName),
 1631    findall(Term, pack_dir_info(PackDir, _, Term), Info),
 1632    option(pack(Pack), Info, DirName),
 1633    option(title(Title), Info, '<no title>'),
 1634    option(version(Version), Info, '<no version>'),
 1635    option(download(URL), Info, '<no download url>'),
 1636    findall(Req, member(requires(Req), Info), Requires),
 1637    findall(Prv, member(provides(Prv), Info), Provides),
 1638    findall(Cfl, member(conflicts(Cfl), Info), Conflicts),
 1639    (   have_git,
 1640        is_git_directory(PackDir)
 1641    ->  git_hash(Hash, [directory(PackDir)]),
 1642        IsGit = true
 1643    ;   Hash = '-',
 1644        IsGit = false
 1645    ).
 1646
 1647
 1648		 /*******************************
 1649		 *        PROLOG VERSIONS	*
 1650		 *******************************/
 prolog_description(-Description) is det
Provide a description of the running Prolog system. Version terms:
To be done
- : establish a language for features. Sync with library(prolog_versions)
 1661prolog_description([prolog(swi(Version))]) :-
 1662    prolog_version(Version).
 1663
 1664prolog_version(Version) :-
 1665    current_prolog_flag(version_git, Version),
 1666    !.
 1667prolog_version(Version) :-
 1668    prolog_version_dotted(Version).
 1669
 1670prolog_version_dotted(Version) :-
 1671    current_prolog_flag(version_data, swi(Major, Minor, Patch, _)),
 1672    VNumbers = [Major, Minor, Patch],
 1673    atomic_list_concat(VNumbers, '.', Version).
 is_prolog_token(+Token) is semidet
True when Token describes a property of the target Prolog system.
 1680is_prolog_token(Token), cmp(Token, prolog, _Cmp, _Version) => true.
 1681is_prolog_token(prolog:_Feature) => true.
 1682is_prolog_token(_) => fail.
 prolog_satisfies(+Token) is semidet
True when the running Prolog system satisfies token. Processes requires(Token) terms for
See also
- require_prolog_version/2.
 1697prolog_satisfies(Token), cmp(Token, prolog, Cmp, ReqVersion) =>
 1698    prolog_version(CurrentVersion),
 1699    cmp_versions(Cmp, CurrentVersion, ReqVersion).
 1700prolog_satisfies(prolog:library(Lib)), atom(Lib) =>
 1701    exists_source(library(Lib)).
 1702prolog_satisfies(prolog:Feature), atom(Feature) =>
 1703    current_prolog_flag(Feature, true).
 1704prolog_satisfies(prolog:Feature), flag_value_feature(Feature, Flag, Value) =>
 1705    current_prolog_flag(Flag, Value).
 1706
 1707flag_value_feature(Feature, Flag, Value) :-
 1708    compound(Feature),
 1709    compound_name_arguments(Feature, Flag, [Value]).
 1710
 1711
 1712                 /*******************************
 1713                 *             INFO             *
 1714                 *******************************/
 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.
 1728:- if(exists_source(library(archive))). 1729ensure_loaded_archive :-
 1730    current_predicate(archive_open/3),
 1731    !.
 1732ensure_loaded_archive :-
 1733    use_module(library(archive)).
 1734
 1735pack_archive_info(Archive, Pack, [archive_size(Bytes)|Info], Strip) :-
 1736    ensure_loaded_archive,
 1737    size_file(Archive, Bytes),
 1738    setup_call_cleanup(
 1739        archive_open(Archive, Handle, []),
 1740        (   repeat,
 1741            (   archive_next_header(Handle, InfoFile)
 1742            ->  true
 1743            ;   !, fail
 1744            )
 1745        ),
 1746        archive_close(Handle)),
 1747    file_base_name(InfoFile, 'pack.pl'),
 1748    atom_concat(Prefix, 'pack.pl', InfoFile),
 1749    strip_option(Prefix, Pack, Strip),
 1750    setup_call_cleanup(
 1751        archive_open_entry(Handle, Stream),
 1752        read_stream_to_terms(Stream, Info),
 1753        close(Stream)),
 1754    !,
 1755    must_be(ground, Info),
 1756    maplist(valid_term(pack_info_term), Info).
 1757:- else. 1758pack_archive_info(_, _, _, _) :-
 1759    existence_error(library, archive).
 1760:- endif. 1761pack_archive_info(_, _, _, _) :-
 1762    existence_error(pack_file, 'pack.pl').
 1763
 1764strip_option('', _, []) :- !.
 1765strip_option('./', _, []) :- !.
 1766strip_option(Prefix, Pack, [remove_prefix(Prefix)]) :-
 1767    atom_concat(PrefixDir, /, Prefix),
 1768    file_base_name(PrefixDir, Base),
 1769    (   Base == Pack
 1770    ->  true
 1771    ;   pack_version_file(Pack, _, Base)
 1772    ->  true
 1773    ;   \+ sub_atom(PrefixDir, _, _, _, /)
 1774    ).
 1775
 1776read_stream_to_terms(Stream, Terms) :-
 1777    read(Stream, Term0),
 1778    read_stream_to_terms(Term0, Stream, Terms).
 1779
 1780read_stream_to_terms(end_of_file, _, []) :- !.
 1781read_stream_to_terms(Term0, Stream, [Term0|Terms]) :-
 1782    read(Stream, Term1),
 1783    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.
 1791pack_git_info(GitDir, Hash, [git(true), installed_size(Bytes)|Info]) :-
 1792    exists_directory(GitDir),
 1793    !,
 1794    git_ls_tree(Entries, [directory(GitDir)]),
 1795    git_hash(Hash, [directory(GitDir)]),
 1796    maplist(arg(4), Entries, Sizes),
 1797    sum_list(Sizes, Bytes),
 1798    dir_metadata(GitDir, Info).
 1799
 1800dir_metadata(GitDir, Info) :-
 1801    directory_file_path(GitDir, 'pack.pl', InfoFile),
 1802    read_file_to_terms(InfoFile, Info, [encoding(utf8)]),
 1803    must_be(ground, Info),
 1804    maplist(valid_term(pack_info_term), Info).
 download_file_sanity_check(+Archive, +Pack, +Info) is semidet
Perform basic sanity checks on DownloadFile
 1810download_file_sanity_check(Archive, Pack, Info) :-
 1811    info_field(name(PackName), Info),
 1812    info_field(version(PackVersion), Info),
 1813    pack_version_file(PackFile, FileVersion, Archive),
 1814    must_match([Pack, PackName, PackFile], name),
 1815    must_match([PackVersion, FileVersion], version).
 1816
 1817info_field(Field, Info) :-
 1818    memberchk(Field, Info),
 1819    ground(Field),
 1820    !.
 1821info_field(Field, _Info) :-
 1822    functor(Field, FieldName, _),
 1823    print_message(error, pack(missing(FieldName))),
 1824    fail.
 1825
 1826must_match(Values, _Field) :-
 1827    sort(Values, [_]),
 1828    !.
 1829must_match(Values, Field) :-
 1830    print_message(error, pack(conflict(Field, Values))),
 1831    fail.
 1832
 1833
 1834                 /*******************************
 1835                 *         INSTALLATION         *
 1836                 *******************************/
 prepare_pack_dir(+Dir, +Options)
Prepare for installing the package into Dir. This
 1850prepare_pack_dir(Dir, Options) :-
 1851    exists_directory(Dir),
 1852    !,
 1853    (   empty_directory(Dir)
 1854    ->  true
 1855    ;   remove_existing_pack(Dir, Options)
 1856    ->  make_directory(Dir)
 1857    ).
 1858prepare_pack_dir(Dir, _) :-
 1859    (   read_link(Dir, _, _)
 1860    ;   access_file(Dir, exist)
 1861    ),
 1862    !,
 1863    delete_file(Dir),
 1864    make_directory(Dir).
 1865prepare_pack_dir(Dir, _) :-
 1866    make_directory(Dir).
 empty_directory(+Directory) is semidet
True if Directory is empty (holds no files or sub-directories).
 1872empty_directory(Dir) :-
 1873    \+ ( directory_files(Dir, Entries),
 1874         member(Entry, Entries),
 1875         \+ special(Entry)
 1876       ).
 1877
 1878special(.).
 1879special(..).
 remove_existing_pack(+PackDir, +Options) is semidet
Remove a possible existing pack directory if the option upgrade(true) is present. This is used to remove an old installation before unpacking a new archive, copy or link a directory with the new contents.
 1888remove_existing_pack(PackDir, Options) :-
 1889    exists_directory(PackDir),
 1890    !,
 1891    (   (   option(upgrade(true), Options)
 1892        ;   confirm(remove_existing_pack(PackDir), yes, Options)
 1893        )
 1894    ->  delete_directory_and_contents(PackDir)
 1895    ;   print_message(error, pack(directory_exists(PackDir))),
 1896        fail
 1897    ).
 1898remove_existing_pack(_, _).
 pack_download_from_url(+URL, +PackDir, +Pack, +Options)
Download a package from a remote source. For git repositories, we simply clone. Archives are downloaded. Options:
git(true)
Assume URL refers to a git repository.
pack_dir(-Dir)
Dir is unified with the location where the pack is installed.
To be done
- We currently use the built-in HTTP client. For complete coverage, we should consider using an external (e.g., curl) if available.
 1914pack_download_from_url(URL, PackTopDir, Pack, Options) :-
 1915    option(git(true), Options),
 1916    !,
 1917    directory_file_path(PackTopDir, Pack, PackDir),
 1918    prepare_pack_dir(PackDir, Options),
 1919    (   option(branch(Branch), Options)
 1920    ->  Extra = ['--branch', Branch]
 1921    ;   Extra = []
 1922    ),
 1923    run_process(path(git), [clone, URL, PackDir|Extra], []),
 1924    git_checkout_version(PackDir, [update(false)|Options]),
 1925    option(pack_dir(PackDir), Options, _).
 1926pack_download_from_url(URL, PackTopDir, Pack, Options) :-
 1927    download_url(URL),
 1928    !,
 1929    directory_file_path(PackTopDir, Pack, PackDir),
 1930    prepare_pack_dir(PackDir, Options),
 1931    pack_download_dir(PackTopDir, DownLoadDir),
 1932    download_file(URL, Pack, DownloadBase, Options),
 1933    directory_file_path(DownLoadDir, DownloadBase, DownloadFile),
 1934    (   option(insecure(true), Options, false)
 1935    ->  TLSOptions = [cert_verify_hook(ssl_verify)]
 1936    ;   TLSOptions = []
 1937    ),
 1938    print_message(informational, pack(download(begin, Pack, URL, DownloadFile))),
 1939    setup_call_cleanup(
 1940        http_open(URL, In, TLSOptions),
 1941        setup_call_cleanup(
 1942            open(DownloadFile, write, Out, [type(binary)]),
 1943            copy_stream_data(In, Out),
 1944            close(Out)),
 1945        close(In)),
 1946    print_message(informational, pack(download(end, Pack, URL, DownloadFile))),
 1947    pack_archive_info(DownloadFile, Pack, Info, _),
 1948    (   option(git_url(GitURL), Options)
 1949    ->  Origin = GitURL                 % implicit download from git.
 1950    ;   download_file_sanity_check(DownloadFile, Pack, Info),
 1951        Origin = URL
 1952    ),
 1953    pack_unpack_from_local(DownloadFile, PackTopDir, Pack, PackDir, Options),
 1954    pack_assert(PackDir, archive(DownloadFile, Origin)),
 1955    option(pack_dir(PackDir), Options, _).
 1956pack_download_from_url(URL, PackTopDir, Pack, Options) :-
 1957    local_uri_file_name(URL, File),
 1958    !,
 1959    pack_unpack_from_local(File, PackTopDir, Pack, PackDir, Options),
 1960    pack_assert(PackDir, archive(File, URL)),
 1961    option(pack_dir(PackDir), Options, _).
 1962pack_download_from_url(URL, _PackTopDir, _Pack, _Options) :-
 1963    domain_error(url, URL).
 git_checkout_version(+PackDir, +Options) is det
Given a checked out version of a repository, put the repo at the desired version. Options:
commit(+Commit)
Target commit or 'HEAD'. If 'HEAD', get the HEAD of the explicit (option branch(Branch)), current or default branch. If the commit is a hash and it is the tip of a branch, checkout this branch. Else simply checkout the hash.
branch(+Branch)
Used with commit('HEAD').
version(+Version)
Checkout a tag. If there is a tag matching Version use that, otherwise try to find a tag that ends with Version and demand the prefix to be letters, optionally followed by a dash or underscore. Examples: 2.1, V2.1, v_2.1.
update(true)
If none of the above is given update the repo. If it is on a branch, pull. Else, put it on the default branch and pull.
 1987git_checkout_version(PackDir, Options) :-
 1988    option(commit('HEAD'), Options),
 1989    option(branch(Branch), Options),
 1990    !,
 1991    git_ensure_on_branch(PackDir, Branch),
 1992    run_process(path(git), ['-C', PackDir, pull], []).
 1993git_checkout_version(PackDir, Options) :-
 1994    option(commit('HEAD'), Options),
 1995    git_current_branch(_, [directory(PackDir)]),
 1996    !,
 1997    run_process(path(git), ['-C', PackDir, pull], []).
 1998git_checkout_version(PackDir, Options) :-
 1999    option(commit('HEAD'), Options),
 2000    !,
 2001    git_default_branch(Branch, [directory(PackDir)]),
 2002    git_ensure_on_branch(PackDir, Branch),
 2003    run_process(path(git), ['-C', PackDir, pull], []).
 2004git_checkout_version(PackDir, Options) :-
 2005    option(commit(Hash), Options),
 2006    run_process(path(git), ['-C', PackDir, fetch], []),
 2007    git_branches(Branches, [contains(Hash), directory(PackDir)]),
 2008    git_process_output(['-C', PackDir, 'rev-parse' | Branches],
 2009                       read_lines_to_atoms(Commits),
 2010                       []),
 2011    nth1(I, Commits, Hash),
 2012    nth1(I, Branches, Branch),
 2013    !,
 2014    git_ensure_on_branch(PackDir, Branch).
 2015git_checkout_version(PackDir, Options) :-
 2016    option(commit(Hash), Options),
 2017    !,
 2018    run_process(path(git), ['-C', PackDir, checkout, '--quiet', Hash], []).
 2019git_checkout_version(PackDir, Options) :-
 2020    option(version(Version), Options),
 2021    !,
 2022    git_tags(Tags, [directory(PackDir)]),
 2023    (   memberchk(Version, Tags)
 2024    ->  Tag = Version
 2025    ;   member(Tag, Tags),
 2026        sub_atom(Tag, B, _, 0, Version),
 2027        sub_atom(Tag, 0, B, _, Prefix),
 2028        version_prefix(Prefix)
 2029    ->  true
 2030    ;   existence_error(version_tag, Version)
 2031    ),
 2032    run_process(path(git), ['-C', PackDir, checkout, Tag], []).
 2033git_checkout_version(_PackDir, Options) :-
 2034    option(fresh(true), Options),
 2035    !.
 2036git_checkout_version(PackDir, _Options) :-
 2037    git_current_branch(_, [directory(PackDir)]),
 2038    !,
 2039    run_process(path(git), ['-C', PackDir, pull], []).
 2040git_checkout_version(PackDir, _Options) :-
 2041    git_default_branch(Branch, [directory(PackDir)]),
 2042    git_ensure_on_branch(PackDir, Branch),
 2043    run_process(path(git), ['-C', PackDir, pull], []).
 git_ensure_on_branch(+PackDir, +Branch) is det
Ensure PackDir is on Branch.
 2049git_ensure_on_branch(PackDir, Branch) :-
 2050    git_current_branch(Branch, [directory(PackDir)]),
 2051    !.
 2052git_ensure_on_branch(PackDir, Branch) :-
 2053    run_process(path(git), ['-C', PackDir, checkout, Branch], []).
 2054
 2055read_lines_to_atoms(Atoms, In) :-
 2056    read_line_to_string(In, Line),
 2057    (   Line == end_of_file
 2058    ->  Atoms = []
 2059    ;   atom_string(Atom, Line),
 2060        Atoms = [Atom|T],
 2061        read_lines_to_atoms(T, In)
 2062    ).
 2063
 2064version_prefix(Prefix) :-
 2065    atom_codes(Prefix, Codes),
 2066    phrase(version_prefix, Codes).
 2067
 2068version_prefix -->
 2069    [C],
 2070    { code_type(C, alpha) },
 2071    !,
 2072    version_prefix.
 2073version_prefix -->
 2074    "-".
 2075version_prefix -->
 2076    "_".
 2077version_prefix -->
 2078    "".
 download_file(+URL, +Pack, -File, +Options) is det
Determine the file into which to download URL. The second clause deals with GitHub downloads from a release tag.
 2085download_file(URL, Pack, File, Options) :-
 2086    option(version(Version), Options),
 2087    !,
 2088    file_name_extension(_, Ext, URL),
 2089    format(atom(File), '~w-~w.~w', [Pack, Version, Ext]).
 2090download_file(URL, Pack, File, _) :-
 2091    file_base_name(URL,Basename),
 2092    no_int_file_name_extension(Tag,Ext,Basename),
 2093    tag_version(Tag,Version),
 2094    !,
 2095    format(atom(File0), '~w-~w', [Pack, Version]),
 2096    file_name_extension(File0, Ext, File).
 2097download_file(URL, _, File, _) :-
 2098    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 in the web server.
 2106:- public pack_url_file/2. 2107pack_url_file(URL, FileID) :-
 2108    github_release_url(URL, Pack, Version),
 2109    !,
 2110    download_file(URL, Pack, FileID, [version(Version)]).
 2111pack_url_file(URL, FileID) :-
 2112    file_base_name(URL, FileID).
 2113
 2114%   ssl_verify(+SSL, +ProblemCert, +AllCerts, +FirstCert, +Error)
 2115%
 2116%   Used if insecure(true)  is  given   to  pack_install/2.  Accepts any
 2117%   certificate.
 2118
 2119:- public ssl_verify/5. 2120ssl_verify(_SSL,
 2121           _ProblemCertificate, _AllCertificates, _FirstCertificate,
 2122           _Error).
 2123
 2124pack_download_dir(PackTopDir, DownLoadDir) :-
 2125    directory_file_path(PackTopDir, 'Downloads', DownLoadDir),
 2126    (   exists_directory(DownLoadDir)
 2127    ->  true
 2128    ;   make_directory(DownLoadDir)
 2129    ),
 2130    (   access_file(DownLoadDir, write)
 2131    ->  true
 2132    ;   permission_error(write, directory, DownLoadDir)
 2133    ).
 download_url(@URL) is semidet
True if URL looks like a URL we can download from. Noet that urls like ftp:// are also download URLs, but we cannot download from them.
 2141download_url(URL) :-
 2142    atom(URL),
 2143    uri_components(URL, Components),
 2144    uri_data(scheme, Components, Scheme),
 2145    download_scheme(Scheme).
 2146
 2147download_scheme(http).
 2148download_scheme(https).
 pack_post_install(+Pack, +PackDir, +Options) is det
Process post installation work. Steps:
 2158pack_post_install(Pack, PackDir, Options) :-
 2159    post_install_foreign(Pack, PackDir, Options),
 2160    post_install_autoload(PackDir, Options),
 2161    attach_packs(PackDir, [duplicate(warning)]).
 pack_rebuild is det
 pack_rebuild(+Pack) is det
Rebuild possible foreign components of Pack. The predicate pack_rebuild/0 rebuilds all registered packs.
 2169pack_rebuild :-
 2170    forall(current_pack(Pack),
 2171           ( print_message(informational, pack(rebuild(Pack))),
 2172             pack_rebuild(Pack)
 2173           )).
 2174
 2175pack_rebuild(Pack) :-
 2176    current_pack(Pack, PackDir),
 2177    !,
 2178    post_install_foreign(Pack, PackDir, [rebuild(true)]).
 2179pack_rebuild(Pack) :-
 2180    unattached_pack(Pack, PackDir),
 2181    !,
 2182    post_install_foreign(Pack, PackDir, [rebuild(true)]).
 2183pack_rebuild(Pack) :-
 2184    existence_error(pack, Pack).
 2185
 2186unattached_pack(Pack, BaseDir) :-
 2187    directory_file_path(Pack, 'pack.pl', PackFile),
 2188    absolute_file_name(pack(PackFile), PackPath,
 2189                       [ access(read),
 2190                         file_errors(fail)
 2191                       ]),
 2192    file_directory_name(PackPath, BaseDir).
 post_install_foreign(+Pack, +PackDir, +Options) is det
Install foreign parts of the package. Options:
rebuild(When)
Determine when to rebuild. Possible values:
if_absent
Only rebuild if we have no existing foreign library. This is the default.
true
Always rebuild.
 2208post_install_foreign(Pack, PackDir, Options) :-
 2209    is_foreign_pack(PackDir, _),
 2210    !,
 2211    (   pack_info_term(PackDir, pack_version(Version))
 2212    ->  true
 2213    ;   Version = 1
 2214    ),
 2215    option(rebuild(Rebuild), Options, if_absent),
 2216    current_prolog_flag(arch, Arch),
 2217    prolog_version_dotted(PrologVersion),
 2218    (   Rebuild == if_absent,
 2219        foreign_present(PackDir, Arch)
 2220    ->  print_message(informational, pack(kept_foreign(Pack, Arch))),
 2221        (   pack_status_dir(PackDir, built(Arch, _, _))
 2222        ->  true
 2223        ;   pack_assert(PackDir, built(Arch, PrologVersion, downloaded))
 2224        )
 2225    ;   BuildSteps0 = [[dependencies], [configure], build, install, [test]],
 2226        (   Rebuild == true
 2227        ->  BuildSteps1 = [distclean|BuildSteps0]
 2228        ;   BuildSteps1 = BuildSteps0
 2229        ),
 2230        (   option(test(false), Options)
 2231        ->  delete(BuildSteps1, [test], BuildSteps2)
 2232        ;   BuildSteps2 = BuildSteps1
 2233        ),
 2234        (   option(clean(true), Options)
 2235        ->  append(BuildSteps2, [[clean]], BuildSteps)
 2236        ;   BuildSteps = BuildSteps2
 2237        ),
 2238        build_steps(BuildSteps, PackDir, [pack_version(Version)|Options]),
 2239        pack_assert(PackDir, built(Arch, PrologVersion, built))
 2240    ).
 2241post_install_foreign(_, _, _).
 foreign_present(+PackDir, +Arch) is semidet
True if we find one or more modules in the pack lib directory for the current architecture.
To be done
- Does not check that these can be loaded, nor whether all required modules are present.
 2252foreign_present(PackDir, Arch) :-
 2253    atomic_list_concat([PackDir, '/lib'], ForeignBaseDir),
 2254    exists_directory(ForeignBaseDir),
 2255    !,
 2256    atomic_list_concat([PackDir, '/lib/', Arch], ForeignDir),
 2257    exists_directory(ForeignDir),
 2258    current_prolog_flag(shared_object_extension, Ext),
 2259    atomic_list_concat([ForeignDir, '/*.', Ext], Pattern),
 2260    expand_file_name(Pattern, Files),
 2261    Files \== [].
 is_foreign_pack(+PackDir, -Type) is nondet
True when PackDir contains files that indicate the need for a specific class of build tools indicated by Type.
 2268is_foreign_pack(PackDir, Type) :-
 2269    foreign_file(File, Type),
 2270    directory_file_path(PackDir, File, Path),
 2271    exists_file(Path).
 2272
 2273foreign_file('CMakeLists.txt', cmake).
 2274foreign_file('configure',      configure).
 2275foreign_file('configure.in',   autoconf).
 2276foreign_file('configure.ac',   autoconf).
 2277foreign_file('Makefile.am',    automake).
 2278foreign_file('Makefile',       make).
 2279foreign_file('makefile',       make).
 2280foreign_file('conanfile.txt',  conan).
 2281foreign_file('conanfile.py',   conan).
 2282
 2283
 2284                 /*******************************
 2285                 *           AUTOLOAD           *
 2286                 *******************************/
 post_install_autoload(+PackDir, +Options)
Create an autoload index if the package demands such.
 2292post_install_autoload(PackDir, Options) :-
 2293    is_autoload_pack(PackDir, Options),
 2294    !,
 2295    directory_file_path(PackDir, prolog, PrologLibDir),
 2296    make_library_index(PrologLibDir).
 2297post_install_autoload(_, _).
 2298
 2299is_autoload_pack(PackDir, Options) :-
 2300    option(autoload(true), Options, true),
 2301    pack_info_term(PackDir, autoload(true)).
 2302
 2303
 2304                 /*******************************
 2305                 *            UPGRADE           *
 2306                 *******************************/
 pack_upgrade(+Pack) is semidet
Upgrade Pack. Shorthand for pack_install(Pack, [upgrade(true)]).
 2312pack_upgrade(Pack) :-
 2313    pack_install(Pack, [upgrade(true)]).
 2314
 2315
 2316                 /*******************************
 2317                 *            REMOVE            *
 2318                 *******************************/
 pack_remove(+Name) is det
 pack_remove(+Name, +Options) is det
Remove the indicated package. If packages depend (indirectly) on this pack, ask to remove these as well. Options:
interactive(false)
Do not prompt the user.
dependencies(Boolean)
If true delete dependencies without asking.
 2331pack_remove(Pack) :-
 2332    pack_remove(Pack, []).
 2333
 2334pack_remove(Pack, Options) :-
 2335    option(dependencies(false), Options),
 2336    !,
 2337    pack_remove_forced(Pack).
 2338pack_remove(Pack, Options) :-
 2339    (   dependents(Pack, Deps)
 2340    ->  (   option(dependencies(true), Options)
 2341        ->  true
 2342        ;   confirm_remove(Pack, Deps, Delete, Options)
 2343        ),
 2344        forall(member(P, Delete), pack_remove_forced(P))
 2345    ;   pack_remove_forced(Pack)
 2346    ).
 2347
 2348pack_remove_forced(Pack) :-
 2349    catch('$pack_detach'(Pack, BaseDir),
 2350          error(existence_error(pack, Pack), _),
 2351          fail),
 2352    !,
 2353    print_message(informational, pack(remove(BaseDir))),
 2354    delete_directory_and_contents(BaseDir).
 2355pack_remove_forced(Pack) :-
 2356    unattached_pack(Pack, BaseDir),
 2357    !,
 2358    delete_directory_and_contents(BaseDir).
 2359pack_remove_forced(Pack) :-
 2360    print_message(informational, error(existence_error(pack, Pack),_)).
 2361
 2362confirm_remove(Pack, Deps, Delete, Options) :-
 2363    print_message(warning, pack(depends(Pack, Deps))),
 2364    menu(pack(resolve_remove),
 2365         [ [Pack]      = remove_only(Pack),
 2366           [Pack|Deps] = remove_deps(Pack, Deps),
 2367           []          = cancel
 2368         ], [], Delete, Options),
 2369    Delete \== [].
 2370
 2371
 2372		 /*******************************
 2373		 *           PUBLISH		*
 2374		 *******************************/
 pack_publish(+Spec, +Options) is det
Publish a package. There are two ways typical ways to call this. We recommend developing a pack in a GIT repository. In this scenario the pack can be published using
?- pack_publish('.', []).

Alternatively, an archive file has been uploaded to a public location. In this scenario we can publish the pack using

?- pack_publish(URL, [])

In both scenarios, pack_publish/2 by default creates an isolated environment and installs the package in this directory from the public URL. On success it triggers the pack server to register the URL as a new pack or a new release of a pack.

Packs may also be published using the app pack, e.g.

swipl pack publish .

Options:

git(Boolean)
If true, and Spec is a git managed directory, install using the remote repo.
sign(Boolean)
Sign the repository with the current version. This runs git tag -s <tag>.
force(Boolean)
Force the git tag. This runs git tag -f <tag>.
branch(+Branch)
Branch used for releases. Defined by git_default_branch/2 if not specified.
register(+Boolean)
If false (default true), perform the installation, but do not upload to the server. This can be used for testing.
isolated(+Boolean)
If true (default), install and build all packages in an isolated package directory. If false, use other packages installed for the environment. The latter may be used to speedup debugging.
pack_directory(+Dir)
Install the temporary packages in Dir. If omitted pack_publish/2 creates a temporary directory and deletes this directory after completion. An explict target Dir is created if it does not exist and is not deleted on completion.
clean(+Boolean)
If true (default), clean the destination directory first
 2427pack_publish(Dir, Options) :-
 2428    \+ download_url(Dir),
 2429    is_git_directory(Dir), !,
 2430    pack_git_info(Dir, _Hash, Metadata),
 2431    prepare_repository(Dir, Metadata, Options),
 2432    (   memberchk(download(URL), Metadata),
 2433        git_url(URL, _)
 2434    ->  true
 2435    ;   option(remote(Remote), Options, origin),
 2436        git_remote_url(Remote, RemoteURL, [directory(Dir)]),
 2437        git_to_https_url(RemoteURL, URL)
 2438    ),
 2439    memberchk(version(Version), Metadata),
 2440    pack_publish_(URL,
 2441                  [ version(Version)
 2442                  | Options
 2443                  ]).
 2444pack_publish(Spec, Options) :-
 2445    pack_publish_(Spec, Options).
 2446
 2447pack_publish_(Spec, Options) :-
 2448    pack_default_options(Spec, Pack, Options, DefOptions),
 2449    option(url(URL), DefOptions),
 2450    valid_publish_url(URL, Options),
 2451    prepare_build_location(Pack, Dir, Clean, Options),
 2452    (   option(register(false), Options)
 2453    ->  InstallOptions = DefOptions
 2454    ;   InstallOptions = [publish(Pack)|DefOptions]
 2455    ),
 2456    call_cleanup(pack_install(Pack,
 2457                              [ pack(Pack)
 2458                              | InstallOptions
 2459                              ]),
 2460                 cleanup_publish(Clean, Dir)).
 2461
 2462cleanup_publish(true, Dir) :-
 2463    !,
 2464    delete_directory_and_contents(Dir).
 2465cleanup_publish(_, _).
 2466
 2467valid_publish_url(URL, Options) :-
 2468    option(register(Register), Options, true),
 2469    (   Register == false
 2470    ->  true
 2471    ;   download_url(URL)
 2472    ->  true
 2473    ;   permission_error(publish, pack, URL)
 2474    ).
 2475
 2476prepare_build_location(Pack, Dir, Clean, Options) :-
 2477    (   option(pack_directory(Dir), Options)
 2478    ->  ensure_directory(Dir),
 2479        (   option(clean(true), Options, true)
 2480        ->  delete_directory_contents(Dir)
 2481        ;   true
 2482        )
 2483    ;   tmp_file(pack, Dir),
 2484        make_directory(Dir),
 2485        Clean = true
 2486    ),
 2487    (   option(isolated(false), Options)
 2488    ->  detach_pack(Pack, _),
 2489        attach_packs(Dir, [search(first)])
 2490    ;   attach_packs(Dir, [replace(true)])
 2491    ).
 prepare_repository(+Dir, +Metadata, +Options) is semidet
Prepare the git repository. If register(false) is provided, this is a test run and therefore we do not need this. Otherwise we demand the working directory to be clean, we tag the current commit and push the current branch.
 2502prepare_repository(_Dir, _Metadata, Options) :-
 2503    option(register(false), Options),
 2504    !.
 2505prepare_repository(Dir, Metadata, Options) :-
 2506    git_dir_must_be_clean(Dir),
 2507    git_must_be_on_default_branch(Dir, Options),
 2508    tag_git_dir(Dir, Metadata, Action, Options),
 2509    confirm(git_push, yes, Options),
 2510    run_process(path(git), ['-C', file(Dir), push ], []),
 2511    (   Action = push_tag(Tag)
 2512    ->  run_process(path(git), ['-C', file(Dir), push, origin, Tag ], [])
 2513    ;   true
 2514    ).
 2515
 2516git_dir_must_be_clean(Dir) :-
 2517    git_describe(Description, [directory(Dir)]),
 2518    (   sub_atom(Description, _, _, 0, '-DIRTY')
 2519    ->  print_message(error, pack(git_not_clean(Dir))),
 2520        fail
 2521    ;   true
 2522    ).
 2523
 2524git_must_be_on_default_branch(Dir, Options) :-
 2525    (   option(branch(Default), Options)
 2526    ->  true
 2527    ;   git_default_branch(Default, [directory(Dir)])
 2528    ),
 2529    git_current_branch(Current, [directory(Dir)]),
 2530    (   Default == Current
 2531    ->  true
 2532    ;   print_message(error,
 2533                      pack(git_branch_not_default(Dir, Default, Current))),
 2534        fail
 2535    ).
 tag_git_dir(+Dir, +Metadata, -Action, +Options) is semidet
Add a version tag to the git repository.
Arguments:
Action- is one of push_tag(Tag) or none
 2544tag_git_dir(Dir, Metadata, Action, Options) :-
 2545    memberchk(version(Version), Metadata),
 2546    atom_concat('V', Version, Tag),
 2547    git_tags(Tags, [directory(Dir)]),
 2548    (   memberchk(Tag, Tags)
 2549    ->  git_tag_is_consistent(Dir, Tag, Action, Options)
 2550    ;   format(string(Message), 'Release ~w', [Version]),
 2551        findall(Opt, git_tag_option(Opt, Options), Argv,
 2552                [ '-m', Message, Tag ]),
 2553        confirm(git_tag(Tag), yes, Options),
 2554        run_process(path(git), ['-C', file(Dir), tag | Argv ], []),
 2555        Action = push_tag(Tag)
 2556    ).
 2557
 2558git_tag_option('-s', Options) :- option(sign(true), Options, true).
 2559git_tag_option('-f', Options) :- option(force(true), Options, true).
 2560
 2561git_tag_is_consistent(Dir, Tag, Action, Options) :-
 2562    format(atom(TagRef), 'refs/tags/~w', [Tag]),
 2563    format(atom(CommitRef), 'refs/tags/~w^{}', [Tag]),
 2564    option(remote(Remote), Options, origin),
 2565    git_ls_remote(Dir, LocalTags, [tags(true)]),
 2566    memberchk(CommitHash-CommitRef, LocalTags),
 2567    (   git_hash(CommitHash, [directory(Dir)])
 2568    ->  true
 2569    ;   print_message(error, pack(git_release_tag_not_at_head(Tag))),
 2570        fail
 2571    ),
 2572    memberchk(TagHash-TagRef, LocalTags),
 2573    git_ls_remote(Remote, RemoteTags, [tags(true)]),
 2574    (   memberchk(RemoteCommitHash-CommitRef, RemoteTags),
 2575        memberchk(RemoteTagHash-TagRef, RemoteTags)
 2576    ->  (   RemoteCommitHash == CommitHash,
 2577            RemoteTagHash == TagHash
 2578        ->  Action = none
 2579        ;   print_message(error, pack(git_tag_out_of_sync(Tag))),
 2580            fail
 2581        )
 2582    ;   Action = push_tag(Tag)
 2583    ).
 git_to_https_url(+GitURL, -HTTP_URL) is semidet
Get the HTTP(s) URL for a git repository, given a git url. Whether or not this is available and how to translate the one into the other depends in the server software.
 2591git_to_https_url(URL, URL) :-
 2592    download_url(URL),
 2593    !.
 2594git_to_https_url(GitURL, URL) :-
 2595    atom_concat('git@github.com:', Repo, GitURL),
 2596    !,
 2597    atom_concat('https://github.com/', Repo, URL).
 2598git_to_https_url(GitURL, _) :-
 2599    print_message(error, pack(git_no_https(GitURL))),
 2600    fail.
 2601
 2602
 2603                 /*******************************
 2604                 *           PROPERTIES         *
 2605                 *******************************/
 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)
 2628pack_property(Pack, Property) :-
 2629    findall(Pack-Property, pack_property_(Pack, Property), List),
 2630    member(Pack-Property, List).            % make det if applicable
 2631
 2632pack_property_(Pack, Property) :-
 2633    pack_info(Pack, _, Property).
 2634pack_property_(Pack, Property) :-
 2635    \+ \+ info_file(Property, _),
 2636    '$pack':pack(Pack, BaseDir),
 2637    access_file(BaseDir, read),
 2638    directory_files(BaseDir, Files),
 2639    member(File, Files),
 2640    info_file(Property, Pattern),
 2641    downcase_atom(File, Pattern),
 2642    directory_file_path(BaseDir, File, InfoFile),
 2643    arg(1, Property, InfoFile).
 2644
 2645info_file(readme(_), 'readme.txt').
 2646info_file(readme(_), 'readme').
 2647info_file(todo(_),   'todo.txt').
 2648info_file(todo(_),   'todo').
 2649
 2650
 2651                 /*******************************
 2652                 *         VERSION LOGIC        *
 2653                 *******************************/
 pack_version_file(-Pack, -Version:atom, +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.
 2662pack_version_file(Pack, Version, GitHubRelease) :-
 2663    atomic(GitHubRelease),
 2664    github_release_url(GitHubRelease, Pack, Version),
 2665    !.
 2666pack_version_file(Pack, Version, Path) :-
 2667    atomic(Path),
 2668    file_base_name(Path, File),
 2669    no_int_file_name_extension(Base, _Ext, File),
 2670    atom_codes(Base, Codes),
 2671    (   phrase(pack_version(Pack, Version), Codes),
 2672        safe_pack_name(Pack)
 2673    ->  true
 2674    ).
 2675
 2676no_int_file_name_extension(Base, Ext, File) :-
 2677    file_name_extension(Base0, Ext0, File),
 2678    \+ atom_number(Ext0, _),
 2679    !,
 2680    Base = Base0,
 2681    Ext = Ext0.
 2682no_int_file_name_extension(File, '', File).
 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.
 2689safe_pack_name(Name) :-
 2690    atom_length(Name, Len),
 2691    Len >= 3,                               % demand at least three length
 2692    atom_codes(Name, Codes),
 2693    maplist(safe_pack_char, Codes),
 2694    !.
 2695
 2696safe_pack_char(C) :- between(0'a, 0'z, C), !.
 2697safe_pack_char(C) :- between(0'A, 0'Z, C), !.
 2698safe_pack_char(C) :- between(0'0, 0'9, C), !.
 2699safe_pack_char(0'_).
 pack_version(-Pack:atom, -Version:atom)// is semidet
True when the input statifies <pack>-<version>
 2705pack_version(Pack, Version) -->
 2706    string(Codes), "-",
 2707    version(Parts),
 2708    !,
 2709    { atom_codes(Pack, Codes),
 2710      atomic_list_concat(Parts, '.', Version)
 2711    }.
 2712
 2713version([H|T]) -->
 2714    version_part(H),
 2715    (   "."
 2716    ->  version(T)
 2717    ;   {T=[]}
 2718    ).
 2719
 2720version_part(*) --> "*", !.
 2721version_part(Int) --> integer(Int).
 2722
 2723
 2724		 /*******************************
 2725		 *           GIT LOGIC		*
 2726		 *******************************/
 2727
 2728have_git :-
 2729    process_which(path(git), _).
 git_url(+URL, -Pack) is semidet
True if URL describes a git url for Pack
 2736git_url(URL, Pack) :-
 2737    uri_components(URL, Components),
 2738    uri_data(scheme, Components, Scheme),
 2739    nonvar(Scheme),                         % must be full URL
 2740    uri_data(path, Components, Path),
 2741    (   Scheme == git
 2742    ->  true
 2743    ;   git_download_scheme(Scheme),
 2744        file_name_extension(_, git, Path)
 2745    ;   git_download_scheme(Scheme),
 2746        catch(git_ls_remote(URL, _, [refs(['HEAD']), error(_)]), _, fail)
 2747    ->  true
 2748    ),
 2749    file_base_name(Path, PackExt),
 2750    (   file_name_extension(Pack, git, PackExt)
 2751    ->  true
 2752    ;   Pack = PackExt
 2753    ),
 2754    (   safe_pack_name(Pack)
 2755    ->  true
 2756    ;   domain_error(pack_name, Pack)
 2757    ).
 2758
 2759git_download_scheme(http).
 2760git_download_scheme(https).
 github_release_url(+URL, -Pack, -Version:atom) 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'
 2769github_release_url(URL, Pack, Version) :-
 2770    uri_components(URL, Components),
 2771    uri_data(authority, Components, 'github.com'),
 2772    uri_data(scheme, Components, Scheme),
 2773    download_scheme(Scheme),
 2774    uri_data(path, Components, Path),
 2775    github_archive_path(Archive,Pack,File),
 2776    atomic_list_concat(Archive, /, Path),
 2777    file_name_extension(Tag, Ext, File),
 2778    github_archive_extension(Ext),
 2779    tag_version(Tag, Version),
 2780    !.
 2781
 2782github_archive_path(['',_User,Pack,archive,File],Pack,File).
 2783github_archive_path(['',_User,Pack,archive,refs,tags,File],Pack,File).
 2784
 2785github_archive_extension(tgz).
 2786github_archive_extension(zip).
 tag_version(+GitTag, -Version) is semidet
True when a GIT tag describes version Version. GitTag must satisfy [vV]?int(\.int)*.
 2793tag_version(Tag, Version) :-
 2794    version_tag_prefix(Prefix),
 2795    atom_concat(Prefix, Version, Tag),
 2796    is_version(Version).
 2797
 2798version_tag_prefix(v).
 2799version_tag_prefix('V').
 2800version_tag_prefix('').
 git_archive_url(+URL, -Archive, +Options) is semidet
If we do not have git installed, some git services offer downloading the code as an archive using HTTP. This predicate makes this translation.
 2809git_archive_url(URL, Archive, Options) :-
 2810    uri_components(URL, Components),
 2811    uri_data(authority, Components, 'github.com'),
 2812    uri_data(path, Components, Path),
 2813    atomic_list_concat(['', User, RepoGit], /, Path),
 2814    $,
 2815    remove_git_ext(RepoGit, Repo),
 2816    git_archive_version(Version, Options),
 2817    atomic_list_concat(['', User, Repo, zip, Version], /, ArchivePath),
 2818    uri_edit([ path(ArchivePath),
 2819               host('codeload.github.com')
 2820             ],
 2821             URL, Archive).
 2822git_archive_url(URL, _, _) :-
 2823    print_message(error, pack(no_git(URL))),
 2824    fail.
 2825
 2826remove_git_ext(RepoGit, Repo) :-
 2827    file_name_extension(Repo, git, RepoGit),
 2828    !.
 2829remove_git_ext(Repo, Repo).
 2830
 2831git_archive_version(Version, Options) :-
 2832    option(commit(Version), Options),
 2833    !.
 2834git_archive_version(Version, Options) :-
 2835    option(branch(Version), Options),
 2836    !.
 2837git_archive_version(Version, Options) :-
 2838    option(version(Version), Options),
 2839    !.
 2840git_archive_version('HEAD', _).
 2841
 2842                 /*******************************
 2843                 *       QUERY CENTRAL DB       *
 2844                 *******************************/
 publish_download(+Infos, +Options) is semidet
 register_downloads(+Infos, +Options) is det
Register our downloads with the pack server.
 2851register_downloads(_, Options) :-
 2852    option(register(false), Options),
 2853    \+ option(do_publish(_), Options),
 2854    !.
 2855register_downloads(Infos, Options) :-
 2856    convlist(download_data, Infos, Data),
 2857    (   Data == []
 2858    ->  true
 2859    ;   query_pack_server(downloaded(Data), Reply, Options),
 2860        (   option(do_publish(Pack), Options)
 2861        ->  (   member(Info, Infos),
 2862                Info.pack == Pack
 2863            ->  true
 2864            ),
 2865            (   Reply = true(Actions),
 2866                memberchk(Pack-Result, Actions)
 2867            ->  (   registered(Result)
 2868                ->  true
 2869                ;   print_message(error, pack(publish_failed(Info, Result))),
 2870                    fail
 2871                )
 2872            ;   print_message(error, pack(publish_failed(Info, false)))
 2873            )
 2874        ;   true
 2875        )
 2876    ).
 2877
 2878registered(git(_URL)).
 2879registered(file(_URL)).
 2880
 2881publish_download(Infos, Options) :-
 2882    select_option(publish(Pack), Options, Options1),
 2883    !,
 2884    register_downloads(Infos, [do_publish(Pack)|Options1]).
 2885publish_download(_Infos, _Options).
 2886
 2887download_data(Info, Data),
 2888    Info.get(git) == true =>                % Git clone
 2889    Data = download(URL, Hash, Metadata),
 2890    URL = Info.get(downloaded),
 2891    pack_git_info(Info.installed, Hash, Metadata).
 2892download_data(Info, Data),
 2893    _{git_url:URL,hash:Hash} :< Info, Hash \== (-) =>
 2894    Data = download(URL, Hash, Metadata),   % Git downloaded as zip
 2895    dir_metadata(Info.installed, Metadata).
 2896download_data(Info, Data) =>                % Archive download.
 2897    Data = download(URL, Hash, Metadata),
 2898    URL = Info.get(downloaded),
 2899    download_url(URL),
 2900    pack_status_dir(Info.installed, archive(Archive, URL)),
 2901    file_sha1(Archive, Hash),
 2902    pack_archive_info(Archive, _Pack, Metadata, _).
 query_pack_server(+Query, -Result, +Options)
Send a Prolog query to the package server and process its results.
 2909query_pack_server(Query, Result, Options) :-
 2910    (   option(server(ServerOpt), Options)
 2911    ->  server_url(ServerOpt, ServerBase)
 2912    ;   setting(server, ServerBase),
 2913        ServerBase \== ''
 2914    ),
 2915    atom_concat(ServerBase, query, Server),
 2916    format(codes(Data), '~q.~n', Query),
 2917    info_level(Informational, Options),
 2918    print_message(Informational, pack(contacting_server(Server))),
 2919    setup_call_cleanup(
 2920        http_open(Server, In,
 2921                  [ post(codes(application/'x-prolog', Data)),
 2922                    header(content_type, ContentType)
 2923                  ]),
 2924        read_reply(ContentType, In, Result),
 2925        close(In)),
 2926    message_severity(Result, Level, Informational),
 2927    print_message(Level, pack(server_reply(Result))).
 2928
 2929server_url(URL0, URL) :-
 2930    uri_components(URL0, Components),
 2931    uri_data(scheme, Components, Scheme),
 2932    var(Scheme),
 2933    !,
 2934    atom_concat('https://', URL0, URL1),
 2935    server_url(URL1, URL).
 2936server_url(URL0, URL) :-
 2937    uri_components(URL0, Components),
 2938    uri_data(path, Components, ''),
 2939    !,
 2940    uri_edit([path('/pack/')], URL0, URL).
 2941server_url(URL, URL).
 2942
 2943read_reply(ContentType, In, Result) :-
 2944    sub_atom(ContentType, 0, _, _, 'application/x-prolog'),
 2945    !,
 2946    set_stream(In, encoding(utf8)),
 2947    read(In, Result).
 2948read_reply(ContentType, In, _Result) :-
 2949    read_string(In, 500, String),
 2950    print_message(error, pack(no_prolog_response(ContentType, String))),
 2951    fail.
 2952
 2953info_level(Level, Options) :-
 2954    option(silent(true), Options),
 2955    !,
 2956    Level = silent.
 2957info_level(informational, _).
 2958
 2959message_severity(true(_), Informational, Informational).
 2960message_severity(false, warning, _).
 2961message_severity(exception(_), error, _).
 2962
 2963
 2964                 /*******************************
 2965                 *        WILDCARD URIs         *
 2966                 *******************************/
 available_download_versions(+URL, -Versions:list(atom)) 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
 2975available_download_versions(URL, Versions) :-
 2976    wildcard_pattern(URL),
 2977    github_url(URL, User, Repo),
 2978    !,
 2979    findall(Version-VersionURL,
 2980            github_version(User, Repo, Version, VersionURL),
 2981            Versions).
 2982available_download_versions(URL, Versions) :-
 2983    wildcard_pattern(URL),
 2984    !,
 2985    file_directory_name(URL, DirURL0),
 2986    ensure_slash(DirURL0, DirURL),
 2987    print_message(informational, pack(query_versions(DirURL))),
 2988    setup_call_cleanup(
 2989        http_open(DirURL, In, []),
 2990        load_html(stream(In), DOM,
 2991                  [ syntax_errors(quiet)
 2992                  ]),
 2993        close(In)),
 2994    findall(MatchingURL,
 2995            absolute_matching_href(DOM, URL, MatchingURL),
 2996            MatchingURLs),
 2997    (   MatchingURLs == []
 2998    ->  print_message(warning, pack(no_matching_urls(URL)))
 2999    ;   true
 3000    ),
 3001    versioned_urls(MatchingURLs, VersionedURLs),
 3002    sort_version_pairs(VersionedURLs, Versions),
 3003    print_message(informational, pack(found_versions(Versions))).
 3004available_download_versions(URL, [Version-URL]) :-
 3005    (   pack_version_file(_Pack, Version0, URL)
 3006    ->  Version = Version0
 3007    ;   Version = '0.0.0'
 3008    ).
 sort_version_pairs(+Pairs, -Sorted) is det
Sort a list of Version-Data by decreasing version.
 3014sort_version_pairs(Pairs, Sorted) :-
 3015    map_list_to_pairs(version_pair_sort_key_, Pairs, Keyed),
 3016    sort(1, @>=, Keyed, SortedKeyed),
 3017    pairs_values(SortedKeyed, Sorted).
 3018
 3019version_pair_sort_key_(Version-_Data, Key) :-
 3020    version_sort_key(Version, Key).
 3021
 3022version_sort_key(Version, Key) :-
 3023    split_string(Version, ".", "", Parts),
 3024    maplist(number_string, Key, Parts),
 3025    !.
 3026version_sort_key(Version, _) :-
 3027    domain_error(version, Version).
 github_url(+URL, -User, -Repo) is semidet
True when URL refers to a github repository.
 3033github_url(URL, User, Repo) :-
 3034    uri_components(URL, uri_components(https,'github.com',Path,_,_)),
 3035    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.
 3043github_version(User, Repo, Version, VersionURI) :-
 3044    atomic_list_concat(['',repos,User,Repo,tags], /, Path1),
 3045    uri_components(ApiUri, uri_components(https,'api.github.com',Path1,_,_)),
 3046    setup_call_cleanup(
 3047      http_open(ApiUri, In,
 3048                [ request_header('Accept'='application/vnd.github.v3+json')
 3049                ]),
 3050      json_read_dict(In, Dicts),
 3051      close(In)),
 3052    member(Dict, Dicts),
 3053    atom_string(Tag, Dict.name),
 3054    tag_version(Tag, Version),
 3055    atom_string(VersionURI, Dict.zipball_url).
 3056
 3057wildcard_pattern(URL) :- sub_atom(URL, _, _, _, *).
 3058wildcard_pattern(URL) :- sub_atom(URL, _, _, _, ?).
 3059
 3060ensure_slash(Dir, DirS) :-
 3061    (   sub_atom(Dir, _, _, 0, /)
 3062    ->  DirS = Dir
 3063    ;   atom_concat(Dir, /, DirS)
 3064    ).
 3065
 3066remove_slash(Dir0, Dir) :-
 3067    Dir0 \== '/',
 3068    atom_concat(Dir1, /, Dir0),
 3069    !,
 3070    remove_slash(Dir1, Dir).
 3071remove_slash(Dir, Dir).
 3072
 3073absolute_matching_href(DOM, Pattern, Match) :-
 3074    xpath(DOM, //a(@href), HREF),
 3075    uri_normalized(HREF, Pattern, Match),
 3076    wildcard_match(Pattern, Match).
 3077
 3078versioned_urls([], []).
 3079versioned_urls([H|T0], List) :-
 3080    file_base_name(H, File),
 3081    (   pack_version_file(_Pack, Version, File)
 3082    ->  List = [Version-H|T]
 3083    ;   List = T
 3084    ),
 3085    versioned_urls(T0, T).
 3086
 3087
 3088                 /*******************************
 3089                 *          DEPENDENCIES        *
 3090                 *******************************/
 pack_provides(?Pack, -Provides) is multi
 pack_requires(?Pack, -Requires) is nondet
 pack_conflicts(?Pack, -Conflicts) is nondet
Provide logical access to pack dependency relations.
 3098pack_provides(Pack, Pack@Version) :-
 3099    current_pack(Pack),
 3100    once(pack_info(Pack, version, version(Version))).
 3101pack_provides(Pack, Provides) :-
 3102    findall(Prv, pack_info(Pack, dependency, provides(Prv)), PrvList),
 3103    member(Provides, PrvList).
 3104
 3105pack_requires(Pack, Requires) :-
 3106    current_pack(Pack),
 3107    findall(Req, pack_info(Pack, dependency, requires(Req)), ReqList),
 3108    member(Requires, ReqList).
 3109
 3110pack_conflicts(Pack, Conflicts) :-
 3111    current_pack(Pack),
 3112    findall(Cfl, pack_info(Pack, dependency, conflicts(Cfl)), CflList),
 3113    member(Conflicts, CflList).
 pack_depends_on(?Pack, ?Dependency) is nondet
True when Pack depends on pack Dependency. This predicate does not deal with transitive dependency.
 3120pack_depends_on(Pack, Dependency) :-
 3121    ground(Pack),
 3122    !,
 3123    pack_requires(Pack, Requires),
 3124    \+ is_prolog_token(Requires),
 3125    pack_provides(Dependency, Provides),
 3126    satisfies_req(Provides, Requires).
 3127pack_depends_on(Pack, Dependency) :-
 3128    ground(Dependency),
 3129    !,
 3130    pack_provides(Dependency, Provides),
 3131    pack_requires(Pack, Requires),
 3132    satisfies_req(Provides, Requires).
 3133pack_depends_on(Pack, Dependency) :-
 3134    current_pack(Pack),
 3135    pack_depends_on(Pack, Dependency).
 dependents(+Pack, -Dependents) is semidet
True when Dependents is a list of packs that (indirectly) depend on Pack.
 3142dependents(Pack, Deps) :-
 3143    setof(Dep, dependent(Pack, Dep, []), Deps).
 3144
 3145dependent(Pack, Dep, Seen) :-
 3146    pack_depends_on(Dep0, Pack),
 3147    \+ memberchk(Dep0, Seen),
 3148    (   Dep = Dep0
 3149    ;   dependent(Dep0, Dep, [Dep0|Seen])
 3150    ).
 validate_dependencies is det
Validate all dependencies, reporting on failures
 3156validate_dependencies :-
 3157    setof(Issue, pack_dependency_issue(_, Issue), Issues),
 3158    !,
 3159    print_message(warning, pack(dependency_issues(Issues))).
 3160validate_dependencies.
 pack_dependency_issue(?Pack, -Issue) is nondet
True when Issue is a dependency issue regarding Pack. Issue is one of
unsatisfied(Pack, Requires)
The requirement Requires of Pack is not fulfilled.
conflicts(Pack, Conflict)
Pack conflicts with Conflict.
 3172pack_dependency_issue(Pack, Issue) :-
 3173    current_pack(Pack),
 3174    pack_dependency_issue_(Pack, Issue).
 3175
 3176pack_dependency_issue_(Pack, unsatisfied(Pack, Requires)) :-
 3177    pack_requires(Pack, Requires),
 3178    (   is_prolog_token(Requires)
 3179    ->  \+ prolog_satisfies(Requires)
 3180    ;   \+ ( pack_provides(_, Provides),
 3181             satisfies_req(Provides, Requires) )
 3182    ).
 3183pack_dependency_issue_(Pack, conflicts(Pack, Conflicts)) :-
 3184    pack_conflicts(Pack, Conflicts),
 3185    (   is_prolog_token(Conflicts)
 3186    ->  prolog_satisfies(Conflicts)
 3187    ;   pack_provides(_, Provides),
 3188        satisfies_req(Provides, Conflicts)
 3189    ).
 3190
 3191
 3192		 /*******************************
 3193		 *      RECORD PACK FACTS	*
 3194		 *******************************/
 pack_assert(+PackDir, ++Fact) is det
Add/update a fact about packs. These facts are stored in PackDir/status.db. Known facts are:
built(Arch, Version, How)
Pack has been built by SWI-Prolog Version for Arch. How is one of built if we built it or downloaded if it was downloaded.
automatic(Boolean)
If true, pack was installed as dependency.
archive(Archive, URL)
Available when the pack was installed by unpacking Archive that was retrieved from URL.
 3210pack_assert(PackDir, Fact) :-
 3211    must_be(ground, Fact),
 3212    findall(Term, pack_status_dir(PackDir, Term), Facts0),
 3213    update_facts(Facts0, Fact, Facts),
 3214    OpenOptions = [encoding(utf8), lock(exclusive)],
 3215    status_file(PackDir, StatusFile),
 3216    (   Facts == Facts0
 3217    ->  true
 3218    ;   Facts0 \== [],
 3219        append(Facts0, New, Facts)
 3220    ->  setup_call_cleanup(
 3221            open(StatusFile, append, Out, OpenOptions),
 3222            maplist(write_fact(Out), New),
 3223            close(Out))
 3224    ;   setup_call_cleanup(
 3225            open(StatusFile, write, Out, OpenOptions),
 3226            ( write_facts_header(Out),
 3227              maplist(write_fact(Out), Facts)
 3228            ),
 3229            close(Out))
 3230    ).
 3231
 3232update_facts([], Fact, [Fact]) :-
 3233    !.
 3234update_facts([H|T], Fact, [Fact|T]) :-
 3235    general_pack_fact(Fact, GenFact),
 3236    general_pack_fact(H, GenTerm),
 3237    GenFact =@= GenTerm,
 3238    !.
 3239update_facts([H|T0], Fact, [H|T]) :-
 3240    update_facts(T0, Fact, T).
 3241
 3242general_pack_fact(built(Arch, _Version, _How), General) =>
 3243    General = built(Arch, _, _).
 3244general_pack_fact(Term, General), compound(Term) =>
 3245    compound_name_arity(Term, Name, Arity),
 3246    compound_name_arity(General, Name, Arity).
 3247general_pack_fact(Term, General) =>
 3248    General = Term.
 3249
 3250write_facts_header(Out) :-
 3251    format(Out, '% Fact status file.  Managed by package manager.~n', []).
 3252
 3253write_fact(Out, Term) :-
 3254    format(Out, '~q.~n', [Term]).
 pack_status(?Pack, ?Fact)
 pack_status_dir(+PackDir, ?Fact)
True when Fact is true about the package in PackDir. Facts are asserted a file status.db.
 3262pack_status(Pack, Fact) :-
 3263    current_pack(Pack, PackDir),
 3264    pack_status_dir(PackDir, Fact).
 3265
 3266pack_status_dir(PackDir, Fact) :-
 3267    det_if(ground(Fact), pack_status_(PackDir, Fact)).
 3268
 3269pack_status_(PackDir, Fact) :-
 3270    status_file(PackDir, StatusFile),
 3271    catch(term_in_file(valid_term(pack_status_term), StatusFile, Fact),
 3272          error(existence_error(source_sink, StatusFile), _),
 3273          fail).
 3274
 3275pack_status_term(built(atom, version, oneof([built,downloaded]))).
 3276pack_status_term(automatic(boolean)).
 3277pack_status_term(archive(atom, atom)).
 update_automatic(+Info) is det
Update the automatic status of a package. If we install it has no automatic status and we install it as a dependency we mark it as automatic. Else, we mark it as non-automatic as it has been installed explicitly.
 3287update_automatic(Info) :-
 3288    _ = Info.get(dependency_for),
 3289    \+ pack_status(Info.installed, automatic(_)),
 3290    !,
 3291    pack_assert(Info.installed, automatic(true)).
 3292update_automatic(Info) :-
 3293    pack_assert(Info.installed, automatic(false)).
 3294
 3295status_file(PackDir, StatusFile) :-
 3296    directory_file_path(PackDir, 'status.db', StatusFile).
 3297
 3298                 /*******************************
 3299                 *        USER INTERACTION      *
 3300                 *******************************/
 3301
 3302:- multifile prolog:message//1.
 menu(Question, +Alternatives, +Default, -Selection, +Options)
 3306menu(_Question, _Alternatives, Default, Selection, Options) :-
 3307    option(interactive(false), Options),
 3308    !,
 3309    Selection = Default.
 3310menu(Question, Alternatives, Default, Selection, _) :-
 3311    length(Alternatives, N),
 3312    between(1, 5, _),
 3313       print_message(query, Question),
 3314       print_menu(Alternatives, Default, 1),
 3315       print_message(query, pack(menu(select))),
 3316       read_selection(N, Choice),
 3317    !,
 3318    (   Choice == default
 3319    ->  Selection = Default
 3320    ;   nth1(Choice, Alternatives, Selection=_)
 3321    ->  true
 3322    ).
 3323
 3324print_menu([], _, _).
 3325print_menu([Value=Label|T], Default, I) :-
 3326    (   Value == Default
 3327    ->  print_message(query, pack(menu(default_item(I, Label))))
 3328    ;   print_message(query, pack(menu(item(I, Label))))
 3329    ),
 3330    I2 is I + 1,
 3331    print_menu(T, Default, I2).
 3332
 3333read_selection(Max, Choice) :-
 3334    get_single_char(Code),
 3335    (   answered_default(Code)
 3336    ->  Choice = default
 3337    ;   code_type(Code, digit(Choice)),
 3338        between(1, Max, Choice)
 3339    ->  true
 3340    ;   print_message(warning, pack(menu(reply(1,Max)))),
 3341        fail
 3342    ).
 confirm(+Question, +Default, +Options) is semidet
Ask for confirmation.
Arguments:
Default- is one of yes, no or none.
 3350confirm(_Question, Default, Options) :-
 3351    Default \== none,
 3352    option(interactive(false), Options, true),
 3353    !,
 3354    Default == yes.
 3355confirm(Question, Default, _) :-
 3356    between(1, 5, _),
 3357       print_message(query, pack(confirm(Question, Default))),
 3358       read_yes_no(YesNo, Default),
 3359    !,
 3360    format(user_error, '~N', []),
 3361    YesNo == yes.
 3362
 3363read_yes_no(YesNo, Default) :-
 3364    get_single_char(Code),
 3365    code_yes_no(Code, Default, YesNo),
 3366    !.
 3367
 3368code_yes_no(0'y, _, yes).
 3369code_yes_no(0'Y, _, yes).
 3370code_yes_no(0'n, _, no).
 3371code_yes_no(0'N, _, no).
 3372code_yes_no(_, none, _) :- !, fail.
 3373code_yes_no(C, Default, Default) :-
 3374    answered_default(C).
 3375
 3376answered_default(0'\r).
 3377answered_default(0'\n).
 3378answered_default(0'\s).
 3379
 3380
 3381                 /*******************************
 3382                 *            MESSAGES          *
 3383                 *******************************/
 3384
 3385:- multifile prolog:message//1. 3386
 3387prolog:message(pack(Message)) -->
 3388    message(Message).
 3389
 3390:- discontiguous
 3391    message//1,
 3392    label//1. 3393
 3394message(invalid_term(pack_info_term, Term)) -->
 3395    [ 'Invalid package meta data: ~q'-[Term] ].
 3396message(invalid_term(pack_status_term, Term)) -->
 3397    [ 'Invalid package status data: ~q'-[Term] ].
 3398message(directory_exists(Dir)) -->
 3399    [ 'Package target directory exists and is not empty:', nl,
 3400      '\t~q'-[Dir]
 3401    ].
 3402message(already_installed(pack(Pack, Version))) -->
 3403    [ 'Pack `~w'' is already installed @~w'-[Pack, Version] ].
 3404message(already_installed(Pack)) -->
 3405    [ 'Pack `~w'' is already installed. Package info:'-[Pack] ].
 3406message(kept_foreign(Pack, Arch)) -->
 3407    [ 'Found foreign libraries for architecture '-[],
 3408      ansi(code, '~q', [Arch]), nl,
 3409      'Use ', ansi(code, '?- pack_rebuild(~q).', [Pack]),
 3410      ' to rebuild from sources'-[]
 3411    ].
 3412message(no_pack_installed(Pack)) -->
 3413    [ 'No pack ~q installed.  Use ?- pack_list(Pattern) to search'-[Pack] ].
 3414message(dependency_issues(Issues)) -->
 3415    [ 'The current set of packs has dependency issues:', nl ],
 3416    dep_issues(Issues).
 3417message(depends(Pack, Deps)) -->
 3418    [ 'The following packs depend on `~w\':'-[Pack], nl ],
 3419    pack_list(Deps).
 3420message(remove(PackDir)) -->
 3421    [ 'Removing ~q and contents'-[PackDir] ].
 3422message(remove_existing_pack(PackDir)) -->
 3423    [ 'Remove old installation in ~q'-[PackDir] ].
 3424message(download_plan(Plan)) -->
 3425    [ ansi(bold, 'Installation plan:', []), nl ],
 3426    install_plan(Plan, Actions),
 3427    install_label(Actions).
 3428message(build_plan(Plan)) -->
 3429    [ ansi(bold, 'The following packs have post install scripts:', []), nl ],
 3430    msg_build_plan(Plan),
 3431    [ nl, ansi(bold, 'Run scripts?', []) ].
 3432message(no_meta_data(BaseDir)) -->
 3433    [ 'Cannot find pack.pl inside directory ~q.  Not a package?'-[BaseDir] ].
 3434message(search_no_matches(Name)) -->
 3435    [ 'Search for "~w", returned no matching packages'-[Name] ].
 3436message(rebuild(Pack)) -->
 3437    [ 'Checking pack "~w" for rebuild ...'-[Pack] ].
 3438message(up_to_date([Pack])) -->
 3439    !,
 3440    [ 'Pack ' ], msg_pack(Pack), [' is up-to-date' ].
 3441message(up_to_date(Packs)) -->
 3442    [ 'Packs ' ], sequence(msg_pack, [', '], Packs), [' are up-to-date' ].
 3443message(installed_can_upgrade(List)) -->
 3444    sequence(msg_can_upgrade_target, [nl], List).
 3445message(new_dependencies(Deps)) -->
 3446    [ 'Found new dependencies after downloading (~p).'-[Deps], nl ].
 3447message(query_versions(URL)) -->
 3448    [ 'Querying "~w" to find new versions ...'-[URL] ].
 3449message(no_matching_urls(URL)) -->
 3450    [ 'Could not find any matching URL: ~q'-[URL] ].
 3451message(found_versions([Latest-_URL|More])) -->
 3452    { length(More, Len) },
 3453    [ '    Latest version: ~w (~D older)'-[Latest, Len] ].
 3454message(build(Pack, PackDir)) -->
 3455    [ ansi(bold, 'Building pack ~w in directory ~w', [Pack, PackDir]) ].
 3456message(contacting_server(Server)) -->
 3457    [ 'Contacting server at ~w ...'-[Server], flush ].
 3458message(server_reply(true(_))) -->
 3459    [ at_same_line, ' ok'-[] ].
 3460message(server_reply(false)) -->
 3461    [ at_same_line, ' done'-[] ].
 3462message(server_reply(exception(E))) -->
 3463    [ 'Server reported the following error:'-[], nl ],
 3464    '$messages':translate_message(E).
 3465message(cannot_create_dir(Alias)) -->
 3466    { findall(PackDir,
 3467              absolute_file_name(Alias, PackDir, [solutions(all)]),
 3468              PackDirs0),
 3469      sort(PackDirs0, PackDirs)
 3470    },
 3471    [ 'Cannot find a place to create a package directory.'-[],
 3472      'Considered:'-[]
 3473    ],
 3474    candidate_dirs(PackDirs).
 3475message(conflict(version, [PackV, FileV])) -->
 3476    ['Version mismatch: pack.pl: '-[]], msg_version(PackV),
 3477    [', file claims version '-[]], msg_version(FileV).
 3478message(conflict(name, [PackInfo, FileInfo])) -->
 3479    ['Pack ~w mismatch: pack.pl: ~p'-[PackInfo]],
 3480    [', file claims ~w: ~p'-[FileInfo]].
 3481message(no_prolog_response(ContentType, String)) -->
 3482    [ 'Expected Prolog response.  Got content of type ~p'-[ContentType], nl,
 3483      '~s'-[String]
 3484    ].
 3485message(download(begin, Pack, _URL, _DownloadFile)) -->
 3486    [ 'Downloading ' ], msg_pack(Pack), [ ' ... ', flush ].
 3487message(download(end, _, _, File)) -->
 3488    { size_file(File, Bytes) },
 3489    [ at_same_line, '~D bytes'-[Bytes] ].
 3490message(no_git(URL)) -->
 3491    [ 'Cannot install from git repository ', url(URL), '.', nl,
 3492      'Cannot find git program and do not know how to download the code', nl,
 3493      'from this git service.  Please install git and retry.'
 3494    ].
 3495message(git_no_https(GitURL)) -->
 3496    [ 'Do not know how to get an HTTP(s) URL for ', url(GitURL) ].
 3497message(git_branch_not_default(Dir, Default, Current)) -->
 3498    [ 'GIT current branch on ', url(Dir), ' is not default.', nl,
 3499      '  Current branch: ', ansi(code, '~w', [Current]),
 3500      ' default: ', ansi(code, '~w', [Default])
 3501    ].
 3502message(git_not_clean(Dir)) -->
 3503    [ 'GIT working directory is dirty: ', url(Dir), nl,
 3504      'Your repository must be clean before publishing.'
 3505    ].
 3506message(git_push) -->
 3507    [ 'Push release to GIT origin?' ].
 3508message(git_tag(Tag)) -->
 3509    [ 'Tag repository with release tag ', ansi(code, '~w', [Tag]) ].
 3510message(git_release_tag_not_at_head(Tag)) -->
 3511    [ 'Release tag ', ansi(code, '~w', [Tag]), ' is not at HEAD.', nl,
 3512      'If you want to update the tag, please run ',
 3513      ansi(code, 'git tag -d ~w', [Tag])
 3514    ].
 3515message(git_tag_out_of_sync(Tag)) -->
 3516    [ 'Release tag ', ansi(code, '~w', [Tag]),
 3517      ' differs from this tag at the origin'
 3518    ].
 3519
 3520message(publish_failed(Info, Reason)) -->
 3521    [ 'Pack ' ], msg_pack(Info), [ ' at version ~w'-[Info.version] ],
 3522    msg_publish_failed(Reason).
 3523
 3524msg_publish_failed(throw(error(permission_error(register,
 3525                                                pack(_),_URL),_))) -->
 3526    [ ' is already registered with a different URL'].
 3527msg_publish_failed(download) -->
 3528    [' was already published?'].
 3529msg_publish_failed(Status) -->
 3530    [ ' failed for unknown reason (~p)'-[Status] ].
 3531
 3532candidate_dirs([]) --> [].
 3533candidate_dirs([H|T]) --> [ nl, '    ~w'-[H] ], candidate_dirs(T).
 3534                                                % Questions
 3535message(resolve_remove) -->
 3536    [ nl, 'Please select an action:', nl, nl ].
 3537message(create_pack_dir) -->
 3538    [ nl, 'Create directory for packages', nl ].
 3539message(menu(item(I, Label))) -->
 3540    [ '~t(~d)~6|   '-[I] ],
 3541    label(Label).
 3542message(menu(default_item(I, Label))) -->
 3543    [ '~t(~d)~6| * '-[I] ],
 3544    label(Label).
 3545message(menu(select)) -->
 3546    [ nl, 'Your choice? ', flush ].
 3547message(confirm(Question, Default)) -->
 3548    message(Question),
 3549    confirm_default(Default),
 3550    [ flush ].
 3551message(menu(reply(Min,Max))) -->
 3552    (  { Max =:= Min+1 }
 3553    -> [ 'Please enter ~w or ~w'-[Min,Max] ]
 3554    ;  [ 'Please enter a number between ~w and ~w'-[Min,Max] ]
 3555    ).
 3556
 3557                                                % support predicates
 3558dep_issues(Issues) -->
 3559    sequence(dep_issue, [nl], Issues).
 3560
 3561dep_issue(unsatisfied(Pack, Requires)) -->
 3562    [ ' - Pack ' ], msg_pack(Pack), [' requires ~p'-[Requires]].
 3563dep_issue(conflicts(Pack, Conflict)) -->
 3564    [ ' - Pack ' ], msg_pack(Pack), [' conflicts with ~p'-[Conflict]].
 install_plan(+Plan, -Actions)// is det
 install_label(+Actions)// is det
Describe the overall installation plan before downloading.
 3571install_label([link]) -->
 3572    !,
 3573    [ ansi(bold, 'Activate pack?', []) ].
 3574install_label([unpack]) -->
 3575    !,
 3576    [ ansi(bold, 'Unpack archive?', []) ].
 3577install_label(_) -->
 3578    [ ansi(bold, 'Download packs?', []) ].
 3579
 3580install_plan([], []) -->
 3581    [].
 3582install_plan([H|T], [AH|AT]) -->
 3583    install_step(H, AH), [nl],
 3584    install_plan(T, AT).
 3585
 3586install_step(Info, keep) -->
 3587    { Info.get(keep) == true },
 3588    !,
 3589    [ '  Keep ' ], msg_pack(Info), [ ' at version ~w'-[Info.version] ],
 3590    msg_can_upgrade(Info).
 3591install_step(Info, Action) -->
 3592    { From = Info.get(upgrade),
 3593      VFrom = From.version,
 3594      VTo = Info.get(version),
 3595      (   cmp_versions(>=, VTo, VFrom)
 3596      ->  Label = ansi(bold,    '  Upgrade ',   [])
 3597      ;   Label = ansi(warning, '  Downgrade ', [])
 3598      )
 3599    },
 3600    [ Label ], msg_pack(Info),
 3601    [ ' from version ~w to ~w'- [From.version, Info.get(version)] ],
 3602    install_from(Info, Action).
 3603install_step(Info, Action) -->
 3604    { _From = Info.get(upgrade) },
 3605    [ '  Upgrade '  ], msg_pack(Info),
 3606    install_from(Info, Action).
 3607install_step(Info, Action) -->
 3608    { Dep = Info.get(dependency_for) },
 3609    [ '  Install ' ], msg_pack(Info),
 3610    [ ' at version ~w as dependency for '-[Info.version],
 3611      ansi(code, '~w', [Dep])
 3612    ],
 3613    install_from(Info, Action),
 3614    msg_downloads(Info).
 3615install_step(Info, Action) -->
 3616    { Info.get(commit) == 'HEAD' },
 3617    !,
 3618    [ '  Install ' ], msg_pack(Info), [ ' at current GIT HEAD'-[] ],
 3619    install_from(Info, Action),
 3620    msg_downloads(Info).
 3621install_step(Info, link) -->
 3622    { Info.get(link) == true,
 3623      uri_file_name(Info.get(url), Dir)
 3624    },
 3625    !,
 3626    [ '  Install ' ], msg_pack(Info), [ ' as symlink to ', url(Dir) ].
 3627install_step(Info, Action) -->
 3628    [ '  Install ' ], msg_pack(Info), [ ' at version ~w'-[Info.get(version)] ],
 3629    install_from(Info, Action),
 3630    msg_downloads(Info).
 3631install_step(Info, Action) -->
 3632    [ '  Install ' ], msg_pack(Info),
 3633    install_from(Info, Action),
 3634    msg_downloads(Info).
 3635
 3636install_from(Info, download) -->
 3637    { download_url(Info.url) },
 3638    !,
 3639    [ ' from ', url(Info.url) ].
 3640install_from(Info, unpack) -->
 3641    [ ' from ', url(Info.url) ].
 3642
 3643msg_downloads(Info) -->
 3644    { Downloads = Info.get(all_downloads),
 3645      Downloads > 0
 3646    },
 3647    [ ansi(comment, ' (downloaded ~D times)', [Downloads]) ],
 3648    !.
 3649msg_downloads(_) -->
 3650    [].
 3651
 3652msg_pack(Pack) -->
 3653    { atom(Pack) },
 3654    !,
 3655    [ ansi(code, '~w', [Pack]) ].
 3656msg_pack(Info) -->
 3657    msg_pack(Info.pack).
 msg_build_plan(+Plan)//
Describe the build plan before running the build steps.
 3663msg_build_plan(Plan) -->
 3664    sequence(build_step, [nl], Plan).
 3665
 3666build_step(Info) -->
 3667    [ '  Build ' ], msg_pack(Info), [' in directory ', url(Info.installed) ].
 3668
 3669msg_can_upgrade_target(Info) -->
 3670    [ '  Pack ' ], msg_pack(Info),
 3671    [ ' is installed at version ~w'-[Info.version] ],
 3672    msg_can_upgrade(Info).
 3673
 3674pack_list([]) --> [].
 3675pack_list([H|T]) -->
 3676    [ '    - Pack ' ],  msg_pack(H), [nl],
 3677    pack_list(T).
 3678
 3679label(remove_only(Pack)) -->
 3680    [ 'Only remove package ~w (break dependencies)'-[Pack] ].
 3681label(remove_deps(Pack, Deps)) -->
 3682    { length(Deps, Count) },
 3683    [ 'Remove package ~w and ~D dependencies'-[Pack, Count] ].
 3684label(create_dir(Dir)) -->
 3685    [ '~w'-[Dir] ].
 3686label(install_from(git(URL))) -->
 3687    !,
 3688    [ 'GIT repository at ~w'-[URL] ].
 3689label(install_from(URL)) -->
 3690    [ '~w'-[URL] ].
 3691label(cancel) -->
 3692    [ 'Cancel' ].
 3693
 3694confirm_default(yes) -->
 3695    [ ' Y/n? ' ].
 3696confirm_default(no) -->
 3697    [ ' y/N? ' ].
 3698confirm_default(none) -->
 3699    [ ' y/n? ' ].
 3700
 3701msg_version(Version) -->
 3702    [ '~w'-[Version] ].
 3703
 3704msg_can_upgrade(Info) -->
 3705    { Latest = Info.get(latest_version) },
 3706    [ ansi(warning, ' (can be upgraded to ~w)', [Latest]) ].
 3707msg_can_upgrade(_) -->
 3708    [].
 3709
 3710
 3711		 /*******************************
 3712		 *              MISC		*
 3713		 *******************************/
 3714
 3715local_uri_file_name(URL, FileName) :-
 3716    uri_file_name(URL, FileName),
 3717    !.
 3718local_uri_file_name(URL, FileName) :-
 3719    uri_components(URL, Components),
 3720    uri_data(scheme, Components, File), File == file,
 3721    uri_data(authority, Components, FileNameEnc),
 3722    uri_data(path, Components, ''),
 3723    uri_encoded(path, FileName, FileNameEnc).
 3724
 3725det_if(Cond, Goal) :-
 3726    (   Cond
 3727    ->  Goal,
 3728        !
 3729    ;   Goal
 3730    ).
 3731
 3732member_nonvar(_, Var) :-
 3733    var(Var),
 3734    !,
 3735    fail.
 3736member_nonvar(E, [E|_]).
 3737member_nonvar(E, [_|T]) :-
 3738    member_nonvar(E, T)