36
37:- module(prolog_pack,
38 [ pack_list_installed/0,
39 pack_info/1, 40 pack_list/1, 41 pack_list/2, 42 pack_search/1, 43 pack_install/1, 44 pack_install/2, 45 pack_install_local/3, 46 pack_upgrade/1, 47 pack_rebuild/1, 48 pack_rebuild/0, 49 pack_remove/1, 50 pack_remove/2, 51 pack_publish/2, 52 pack_property/2 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, +, +). 82
95
96 99
100:- setting(server, atom, 'https://www.swi-prolog.org/pack/',
101 'Server to exchange pack information'). 102
103
104 107
108:- op(900, xfx, @). 109
110:- meta_predicate det_if(0,0). 111
112 115
120
121current_pack(Pack) :-
122 current_pack(Pack, _).
123
124current_pack(Pack, Dir) :-
125 '$pack':pack(Pack, Dir).
126
131
132pack_list_installed :-
133 pack_list('', [installed(true)]),
134 validate_dependencies.
135
139
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. 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).
242
246
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).
271
275
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).
310
315
316pack_info_term(name(atom)). 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)). 322pack_info_term(maintainer(atom, email_or_url)).
323pack_info_term(packager(atom, email_or_url)).
324pack_info_term(pack_version(nonneg)). 325pack_info_term(home(atom)). 326pack_info_term(download(atom)). 327pack_info_term(provides(atom)). 328pack_info_term(requires(dependency)).
329pack_info_term(conflicts(dependency)). 330pack_info_term(replaces(atom)). 331pack_info_term(autoload(boolean)). 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 384
424
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), 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(_, []).
494
501
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).
541
545
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 572
670
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).
693
716
717
718pack_default_options(_Spec, Pack, OptsIn, Options) :- 719 option(already_installed(pack(Pack,_Version)), OptsIn),
720 !,
721 Options = OptsIn.
722pack_default_options(_Spec, Pack, OptsIn, Options) :- 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) :- 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) :- 749 git_url(URL, Pack),
750 !,
751 merge_options([git(true), url(URL)], OptsIn, Options).
752pack_default_options(FileURL, Pack, _, Options) :- 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) :- 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) :- 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) :- 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(_, _, _, []).
793
811
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), 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.
869
881
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 ; \+ option(git(false), Options),
897 is_git_directory(Source)
898 -> remove_existing_pack(PackDir, Options),
899 run_process(path(git), [clone, Source, PackDir], [])
900 ; prepare_pack_dir(PackDir, Options),
901 copy_directory(Source, PackDir)
902 ).
903pack_unpack_from_local(Source, PackTopDir, Name, PackDir, Options) :-
904 exists_file(Source),
905 directory_file_path(PackTopDir, Name, PackDir),
906 prepare_pack_dir(PackDir, Options),
907 pack_unpack(Source, PackDir, Name, Options).
908
915
916:- if(exists_source(library(archive))). 917pack_unpack(Source, PackDir, Pack, Options) :-
918 ensure_loaded_archive,
919 pack_archive_info(Source, Pack, _Info, StripOptions),
920 prepare_pack_dir(PackDir, Options),
921 archive_extract(Source, PackDir,
922 [ exclude(['._*']) 923 | StripOptions
924 ]).
925:- else. 926pack_unpack(_,_,_,_) :-
927 existence_error(library, archive).
928:- endif. 929
935
936pack_install_local(M:Gen, Dir, Options) :-
937 findall(Pack-PackOptions, call(M:Gen, Pack, PackOptions), Pairs),
938 pack_install_set(Pairs, Dir, Options).
939
940pack_install_set(Pairs, Dir, Options) :-
941 must_be(list(pair), Pairs),
942 ensure_directory(Dir),
943 partition(known_media, Pairs, Local, Remote),
944 maplist(pack_options_to_versions, Local, LocalVersions),
945 ( Remote == []
946 -> AllVersions = LocalVersions
947 ; pairs_keys(Remote, Packs),
948 prolog_description(Properties),
949 query_pack_server(versions(Packs, Properties), Result, Options),
950 ( Result = true(RemoteVersions)
951 -> append(LocalVersions, RemoteVersions, AllVersions)
952 ; print_message(error, pack(query_failed(Result))),
953 fail
954 )
955 ),
956 local_packs(Dir, Existing),
957 pack_resolve(Pairs, Existing, AllVersions, Plan, Options),
958 !, 959 Options1 = [pack_directory(Dir)|Options],
960 download_plan(Pairs, Plan, PlanB, Options1),
961 register_downloads(PlanB, Options),
962 maplist(update_automatic, PlanB),
963 build_plan(PlanB, Built, Options1),
964 publish_download(PlanB, Options),
965 work_done(Pairs, Plan, PlanB, Built, Options).
966
973
974known_media(_-Options) :-
975 option(url(_), Options).
976
992
993pack_resolve(Pairs, Existing, Versions, Plan, Options) :-
994 insert_existing(Existing, Versions, AllVersions, Options),
995 phrase(select_version(Pairs, AllVersions,
996 [ plan(PlanA), 997 dependency_for([]) 998 | Options
999 ]),
1000 PlanA),
1001 mark_installed(PlanA, Existing, Plan).
1002
1011
1012:- det(insert_existing/4). 1013insert_existing(Existing, [], Versions, _Options) =>
1014 maplist(existing_to_versions, Existing, Versions).
1015insert_existing(Existing, [Pack-Versions|T0], AllPackVersions, Options),
1016 select(Installed, Existing, Existing2),
1017 Installed.pack == Pack =>
1018 can_upgrade(Installed, Versions, Installed2),
1019 insert_existing_(Installed2, Versions, AllVersions, Options),
1020 AllPackVersions = [Pack-AllVersions|T],
1021 insert_existing(Existing2, T0, T, Options).
1022insert_existing(Existing, [H|T0], AllVersions, Options) =>
1023 AllVersions = [H|T],
1024 insert_existing(Existing, T0, T, Options).
1025
1026existing_to_versions(Installed, Pack-[Version-[Installed]]) :-
1027 Pack = Installed.pack,
1028 Version = Installed.version.
1029
1030insert_existing_(Installed, Versions, AllVersions, Options) :-
1031 option(upgrade(true), Options),
1032 !,
1033 insert_existing_(Installed, Versions, AllVersions).
1034insert_existing_(Installed, Versions, AllVersions, _) :-
1035 AllVersions = [Installed.version-[Installed]|Versions].
1036
1037insert_existing_(Installed, [H|T0], [H|T]) :-
1038 H = V0-_Infos,
1039 cmp_versions(>, V0, Installed.version),
1040 !,
1041 insert_existing_(Installed, T0, T).
1042insert_existing_(Installed, [H0|T], [H|T]) :-
1043 H0 = V0-Infos,
1044 V0 == Installed.version,
1045 !,
1046 H = V0-[Installed|Infos].
1047insert_existing_(Installed, Versions, All) :-
1048 All = [Installed.version-[Installed]|Versions].
1049
1054
1055can_upgrade(Info, [Version-_|_], Info2) :-
1056 cmp_versions(>, Version, Info.version),
1057 !,
1058 Info2 = Info.put(latest_version, Version).
1059can_upgrade(Info, _, Info).
1060
1066
1067mark_installed([], _, []).
1068mark_installed([Info|T], Existing, Plan) :-
1069 ( member(Installed, Existing),
1070 Installed.pack == Info.pack
1071 -> ( ( Installed.git == true
1072 -> Info.git == true,
1073 Installed.hash == Info.hash
1074 ; Version = Info.get(version)
1075 -> Installed.version == Version
1076 )
1077 -> Plan = [Info.put(keep, true)|PlanT] 1078 ; Plan = [Info.put(upgrade, Installed)|PlanT] 1079 )
1080 ; Plan = [Info|PlanT] 1081 ),
1082 mark_installed(T, Existing, PlanT).
1083
1089
1090select_version([], _, _) -->
1091 [].
1092select_version([Pack-PackOptions|More], Versions, Options) -->
1093 { memberchk(Pack-PackVersions, Versions),
1094 member(Version-Infos, PackVersions),
1095 compatible_version(Pack, Version, PackOptions),
1096 member(Info, Infos),
1097 pack_options_compatible_with_info(Info, PackOptions),
1098 pack_satisfies(Pack, Version, Info, Info2, PackOptions),
1099 all_downloads(PackVersions, Downloads)
1100 },
1101 add_to_plan(Info2.put(_{version: Version, all_downloads:Downloads}),
1102 Versions, Options),
1103 select_version(More, Versions, Options).
1104select_version([Pack-_PackOptions|_More], _Versions, _Options) -->
1105 { existence_error(pack, Pack) }. 1106
1107all_downloads(PackVersions, AllDownloads) :-
1108 aggregate_all(sum(Downloads),
1109 ( member(_Version-Infos, PackVersions),
1110 member(Info, Infos),
1111 get_dict(downloads, Info, Downloads)
1112 ),
1113 AllDownloads).
1114
1115add_requirements([], _, _) -->
1116 [].
1117add_requirements([H|T], Versions, Options) -->
1118 { is_prolog_token(H),
1119 !,
1120 prolog_satisfies(H)
1121 },
1122 add_requirements(T, Versions, Options).
1123add_requirements([H|T], Versions, Options) -->
1124 { member(Pack-PackVersions, Versions),
1125 member(Version-Infos, PackVersions),
1126 member(Info, Infos),
1127 ( Provides = @(Pack,Version)
1128 ; member(Provides, Info.get(provides))
1129 ),
1130 satisfies_req(Provides, H),
1131 all_downloads(PackVersions, Downloads)
1132 },
1133 add_to_plan(Info.put(_{version: Version, all_downloads:Downloads}),
1134 Versions, Options),
1135 add_requirements(T, Versions, Options).
1136
1142
1143add_to_plan(Info, _Versions, Options) -->
1144 { option(plan(Plan), Options),
1145 member_nonvar(Planned, Plan),
1146 Planned.pack == Info.pack,
1147 !,
1148 same_version(Planned, Info) 1149 }.
1150add_to_plan(Info, _Versions, _Options) -->
1151 { member(Conflict, Info.get(conflicts)),
1152 is_prolog_token(Conflict),
1153 prolog_satisfies(Conflict),
1154 !,
1155 fail 1156 }.
1157add_to_plan(Info, _Versions, Options) -->
1158 { option(plan(Plan), Options),
1159 member_nonvar(Planned, Plan),
1160 info_conflicts(Info, Planned), 1161 !,
1162 fail
1163 }.
1164add_to_plan(Info, Versions, Options) -->
1165 { select_option(dependency_for(Dep0), Options, Options1),
1166 Options2 = [dependency_for([Info.pack|Dep0])|Options1],
1167 ( Dep0 = [DepFor|_]
1168 -> add_dependency_for(DepFor, Info, Info1)
1169 ; Info1 = Info
1170 )
1171 },
1172 [Info1],
1173 add_requirements(Info.get(requires,[]), Versions, Options2).
1174
1175add_dependency_for(Pack, Info, Info) :-
1176 Old = Info.get(dependency_for),
1177 !,
1178 b_set_dict(dependency_for, Info, [Pack|Old]).
1179add_dependency_for(Pack, Info0, Info) :-
1180 Info = Info0.put(dependency_for, [Pack]).
1181
1182same_version(Info, Info) :-
1183 !.
1184same_version(Planned, Info) :-
1185 Hash = Planned.get(hash),
1186 Hash \== (-),
1187 !,
1188 Hash == Info.get(hash).
1189same_version(Planned, Info) :-
1190 Planned.get(version) == Info.get(version).
1191
1195
1196info_conflicts(Info, Planned) :-
1197 info_conflicts_(Info, Planned),
1198 !.
1199info_conflicts(Info, Planned) :-
1200 info_conflicts_(Planned, Info),
1201 !.
1202
1203info_conflicts_(Info, Planned) :-
1204 member(Conflict, Info.get(conflicts)),
1205 \+ is_prolog_token(Conflict),
1206 info_provides(Planned, Provides),
1207 satisfies_req(Provides, Conflict),
1208 !.
1209
1210info_provides(Info, Provides) :-
1211 ( Provides = Info.pack@Info.version
1212 ; member(Provides, Info.get(provides))
1213 ).
1214
1219
1220pack_satisfies(_Pack, _Version, Info0, Info, Options) :-
1221 option(commit('HEAD'), Options),
1222 !,
1223 Info0.get(git) == true,
1224 Info = Info0.put(commit, 'HEAD').
1225pack_satisfies(_Pack, _Version, Info, Info, Options) :-
1226 option(commit(Commit), Options),
1227 !,
1228 Commit == Info.get(hash).
1229pack_satisfies(Pack, Version, Info, Info, Options) :-
1230 option(version(ReqVersion), Options),
1231 !,
1232 satisfies_version(Pack, Version, ReqVersion).
1233pack_satisfies(_Pack, _Version, Info, Info, _Options).
1234
1236
1237satisfies_version(Pack, Version, ReqVersion) :-
1238 catch(require_version(pack(Pack), Version, ReqVersion),
1239 error(version_error(pack(Pack), Version, ReqVersion),_),
1240 fail).
1241
1245
1246satisfies_req(Token, Token) => true.
1247satisfies_req(@(Token,_), Token) => true.
1248satisfies_req(@(Token,PrvVersion), Req), cmp(Req, Token, Cmp, ReqVersion) =>
1249 cmp_versions(Cmp, PrvVersion, ReqVersion).
1250satisfies_req(_,_) => fail.
1251
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).
1257cmp(Token > Version, Token, >, Version).
1258
1269
1270:- det(pack_options_to_versions/2). 1271pack_options_to_versions(Pack-PackOptions, Pack-Versions) :-
1272 option(versions(Available), PackOptions), !,
1273 maplist(version_url_info(Pack, PackOptions), Available, Versions).
1274pack_options_to_versions(Pack-PackOptions, Pack-[Version-[Info]]) :-
1275 option(url(URL), PackOptions),
1276 findall(Prop, option_info_prop(PackOptions, Prop), Pairs),
1277 dict_create(Info, #,
1278 [ pack-Pack,
1279 url-URL
1280 | Pairs
1281 ]),
1282 Version = Info.get(version, '0.0.0').
1283
1284version_url_info(Pack, PackOptions, Version-URL, Version-[Info]) :-
1285 findall(Prop,
1286 ( option_info_prop(PackOptions, Prop),
1287 Prop \= version-_
1288 ),
1289 Pairs),
1290 dict_create(Info, #,
1291 [ pack-Pack,
1292 url-URL,
1293 version-Version
1294 | Pairs
1295 ]).
1296
1297option_info_prop(PackOptions, Prop-Value) :-
1298 option_info(Prop),
1299 Opt =.. [Prop,Value],
1300 option(Opt, PackOptions).
1301
1302option_info(git).
1303option_info(hash).
1304option_info(version).
1305option_info(branch).
1306option_info(link).
1307
1312
1313compatible_version(Pack, Version, PackOptions) :-
1314 option(version(ReqVersion), PackOptions),
1315 !,
1316 satisfies_version(Pack, Version, ReqVersion).
1317compatible_version(_, _, _).
1318
1323
1324pack_options_compatible_with_info(Info, PackOptions) :-
1325 findall(Prop, option_info_prop(PackOptions, Prop), Pairs),
1326 dict_create(Dict, _, Pairs),
1327 Dict >:< Info.
1328
1336
1337download_plan(_Targets, Plan, Plan, _Options) :-
1338 exclude(installed, Plan, []),
1339 !.
1340download_plan(Targets, Plan0, Plan, Options) :-
1341 confirm(download_plan(Plan0), yes, Options),
1342 maplist(download_from_info(Options), Plan0, Plan1),
1343 plan_unsatisfied_dependencies(Plan1, Deps),
1344 ( Deps == []
1345 -> Plan = Plan1
1346 ; print_message(informational, pack(new_dependencies(Deps))),
1347 prolog_description(Properties),
1348 query_pack_server(versions(Deps, Properties), Result, []),
1349 ( Result = true(Versions)
1350 -> pack_resolve(Targets, Plan1, Versions, Plan2, Options),
1351 !,
1352 download_plan(Targets, Plan2, Plan, Options)
1353 ; print_message(error, pack(query_failed(Result))),
1354 fail
1355 )
1356 ).
1357
1362
1363plan_unsatisfied_dependencies(Plan, Deps) :-
1364 phrase(plan_unsatisfied_dependencies(Plan, Plan), Deps).
1365
1366plan_unsatisfied_dependencies([], _) -->
1367 [].
1368plan_unsatisfied_dependencies([Info|Infos], Plan) -->
1369 { Deps = Info.get(requires) },
1370 plan_unsatisfied_requirements(Deps, Plan),
1371 plan_unsatisfied_dependencies(Infos, Plan).
1372
1373plan_unsatisfied_requirements([], _) -->
1374 [].
1375plan_unsatisfied_requirements([H|T], Plan) -->
1376 { is_prolog_token(H), 1377 prolog_satisfies(H)
1378 },
1379 !,
1380 plan_unsatisfied_requirements(T, Plan).
1381plan_unsatisfied_requirements([H|T], Plan) -->
1382 { member(Info, Plan),
1383 ( ( Version = Info.get(version)
1384 -> Provides = @(Info.get(pack), Version)
1385 ; Provides = Info.get(pack)
1386 )
1387 ; member(Provides, Info.get(provides))
1388 ),
1389 satisfies_req(Provides, H)
1390 }, !,
1391 plan_unsatisfied_requirements(T, Plan).
1392plan_unsatisfied_requirements([H|T], Plan) -->
1393 [H],
1394 plan_unsatisfied_requirements(T, Plan).
1395
1396
1402
1403build_plan(Plan, Ordered, Options) :-
1404 partition(needs_rebuild_from_info(Options), Plan, ToBuild, NoBuild),
1405 maplist(attach_from_info(Options), NoBuild),
1406 ( ToBuild == []
1407 -> Ordered = []
1408 ; order_builds(ToBuild, Ordered),
1409 confirm(build_plan(Ordered), yes, Options),
1410 maplist(exec_plan_rebuild_step(Options), Ordered)
1411 ).
1412
1413needs_rebuild_from_info(Options, Info) :-
1414 needs_rebuild(Info.installed, Options).
1415
1419
1420needs_rebuild(PackDir, Options) :-
1421 ( is_foreign_pack(PackDir, _),
1422 \+ is_built(PackDir, Options)
1423 -> true
1424 ; is_autoload_pack(PackDir, Options),
1425 post_install_autoload(PackDir, Options),
1426 fail
1427 ).
1428
1435
1436is_built(PackDir, _Options) :-
1437 current_prolog_flag(arch, Arch),
1438 prolog_version_dotted(Version), 1439 pack_status_dir(PackDir, built(Arch, Version, _)).
1440
1445
1446order_builds(ToBuild, Ordered) :-
1447 findall(Pack-Dep, dep_edge(ToBuild, Pack, Dep), Edges),
1448 maplist(get_dict(pack), ToBuild, Packs),
1449 vertices_edges_to_ugraph(Packs, Edges, Graph),
1450 ugraph_layers(Graph, Layers),
1451 append(Layers, PackNames),
1452 maplist(pack_info_from_name(ToBuild), PackNames, Ordered).
1453
1454dep_edge(Infos, Pack, Dep) :-
1455 member(Info, Infos),
1456 Pack = Info.pack,
1457 member(Dep, Info.get(dependency_for)),
1458 ( member(DepInfo, Infos),
1459 DepInfo.pack == Dep
1460 -> true
1461 ).
1462
1463:- det(pack_info_from_name/3). 1464pack_info_from_name(Infos, Pack, Info) :-
1465 member(Info, Infos),
1466 Info.pack == Pack,
1467 !.
1468
1472
1473exec_plan_rebuild_step(Options, Info) :-
1474 print_message(informational, pack(build(Info.pack, Info.installed))),
1475 pack_post_install(Info.pack, Info.installed, Options),
1476 attach_from_info(Options, Info).
1477
1481
1482attach_from_info(_Options, Info) :-
1483 Info.get(keep) == true,
1484 !.
1485attach_from_info(Options, Info) :-
1486 ( option(pack_directory(_Parent), Options)
1487 -> pack_attach(Info.installed, [duplicate(replace)])
1488 ; pack_attach(Info.installed, [])
1489 ).
1490
1498
1499download_from_info(Options, Info0, Info), option(dryrun(true), Options) =>
1500 print_term(Info0, [nl(true)]),
1501 Info = Info0.
1502download_from_info(_Options, Info0, Info), installed(Info0) =>
1503 Info = Info0.
1504download_from_info(_Options, Info0, Info),
1505 _{upgrade:OldInfo, git:true} :< Info0,
1506 is_git_directory(OldInfo.installed) =>
1507 PackDir = OldInfo.installed,
1508 git_checkout_version(PackDir, [commit(Info0.hash)]),
1509 reload_info(PackDir, Info0, Info).
1510download_from_info(Options, Info0, Info),
1511 _{upgrade:OldInfo} :< Info0 =>
1512 PackDir = OldInfo.installed,
1513 detach_pack(OldInfo.pack, PackDir),
1514 delete_directory_and_contents(PackDir),
1515 del_dict(upgrade, Info0, _, Info1),
1516 download_from_info(Options, Info1, Info).
1517download_from_info(Options, Info0, Info),
1518 _{url:URL, git:true} :< Info0, \+ have_git =>
1519 git_archive_url(URL, Archive, Options),
1520 download_from_info([git_url(URL)|Options],
1521 Info0.put(_{ url:Archive,
1522 git:false,
1523 git_url:URL
1524 }),
1525 Info1),
1526 1527 ( Info1.get(version) == Info0.get(version),
1528 Hash = Info0.get(hash)
1529 -> Info = Info1.put(hash, Hash)
1530 ; Info = Info1
1531 ).
1532download_from_info(Options, Info0, Info),
1533 _{url:URL} :< Info0 =>
1534 select_option(pack_directory(Dir), Options, Options1),
1535 select_option(version(_), Options1, Options2, _),
1536 download_info_extra(Info0, InstallOptions, Options2),
1537 pack_download_from_url(URL, Dir, Info0.pack,
1538 [ interactive(false),
1539 pack_dir(PackDir)
1540 | InstallOptions
1541 ]),
1542 reload_info(PackDir, Info0, Info).
1543
(Info, [git(true),commit(Hash)|Options], Options) :-
1545 Info.get(git) == true,
1546 !,
1547 Hash = Info.get(commit, 'HEAD').
1548download_info_extra(Info, [link(true)|Options], Options) :-
1549 Info.get(link) == true,
1550 !.
1551download_info_extra(_, Options, Options).
1552
1553installed(Info) :-
1554 _ = Info.get(installed).
1555
1556detach_pack(Pack, PackDir) :-
1557 ( current_pack(Pack, PackDir)
1558 -> '$pack_detach'(Pack, PackDir)
1559 ; true
1560 ).
1561
1568
1569reload_info(_PackDir, Info, Info) :-
1570 _ = Info.get(installed), 1571 !.
1572reload_info(PackDir, Info0, Info) :-
1573 local_pack_info(PackDir, Info1),
1574 Info = Info0.put(installed, PackDir)
1575 .put(downloaded, Info0.url)
1576 .put(Info1).
1577
1582
1583work_done(_, _, _, _, Options),
1584 option(silent(true), Options) =>
1585 true.
1586work_done(Targets, Plan, Plan, [], _Options) =>
1587 convlist(can_upgrade_target(Plan), Targets, CanUpgrade),
1588 ( CanUpgrade == []
1589 -> pairs_keys(Targets, Packs),
1590 print_message(informational, pack(up_to_date(Packs)))
1591 ; print_message(informational, pack(installed_can_upgrade(CanUpgrade)))
1592 ).
1593work_done(_, _, _, _, _) =>
1594 true.
1595
1596can_upgrade_target(Plan, Pack-_, Info) =>
1597 member(Info, Plan),
1598 Info.pack == Pack,
1599 !,
1600 _ = Info.get(latest_version).
1601
1606
1607local_packs(Dir, Packs) :-
1608 findall(Pack, pack_in_subdir(Dir, Pack), Packs).
1609
1610pack_in_subdir(Dir, Info) :-
1611 directory_member(Dir, PackDir,
1612 [ file_type(directory),
1613 hidden(false)
1614 ]),
1615 local_pack_info(PackDir, Info).
1616
1617local_pack_info(PackDir,
1618 #{ pack: Pack,
1619 version: Version,
1620 title: Title,
1621 hash: Hash,
1622 url: URL,
1623 git: IsGit,
1624 requires: Requires,
1625 provides: Provides,
1626 conflicts: Conflicts,
1627 installed: PackDir
1628 }) :-
1629 directory_file_path(PackDir, 'pack.pl', MetaFile),
1630 exists_file(MetaFile),
1631 file_base_name(PackDir, DirName),
1632 findall(Term, pack_dir_info(PackDir, _, Term), Info),
1633 option(pack(Pack), Info, DirName),
1634 option(title(Title), Info, '<no title>'),
1635 option(version(Version), Info, '<no version>'),
1636 option(download(URL), Info, '<no download url>'),
1637 findall(Req, member(requires(Req), Info), Requires),
1638 findall(Prv, member(provides(Prv), Info), Provides),
1639 findall(Cfl, member(conflicts(Cfl), Info), Conflicts),
1640 ( have_git,
1641 is_git_directory(PackDir)
1642 -> git_hash(Hash, [directory(PackDir)]),
1643 IsGit = true
1644 ; Hash = '-',
1645 IsGit = false
1646 ).
1647
1648
1649 1652
1661
1662prolog_description([prolog(swi(Version))]) :-
1663 prolog_version(Version).
1664
1665prolog_version(Version) :-
1666 current_prolog_flag(version_git, Version),
1667 !.
1668prolog_version(Version) :-
1669 prolog_version_dotted(Version).
1670
1671prolog_version_dotted(Version) :-
1672 current_prolog_flag(version_data, swi(Major, Minor, Patch, _)),
1673 VNumbers = [Major, Minor, Patch],
1674 atomic_list_concat(VNumbers, '.', Version).
1675
1680
1681is_prolog_token(Token), cmp(Token, prolog, _Cmp, _Version) => true.
1682is_prolog_token(prolog:_Feature) => true.
1683is_prolog_token(_) => fail.
1684
1697
1698prolog_satisfies(Token), cmp(Token, prolog, Cmp, ReqVersion) =>
1699 prolog_version(CurrentVersion),
1700 cmp_versions(Cmp, CurrentVersion, ReqVersion).
1701prolog_satisfies(prolog:library(Lib)), atom(Lib) =>
1702 exists_source(library(Lib)).
1703prolog_satisfies(prolog:Feature), atom(Feature) =>
1704 current_prolog_flag(Feature, true).
1705prolog_satisfies(prolog:Feature), flag_value_feature(Feature, Flag, Value) =>
1706 current_prolog_flag(Flag, Value).
1707
1708flag_value_feature(Feature, Flag, Value) :-
1709 compound(Feature),
1710 compound_name_arguments(Feature, Flag, [Value]).
1711
1712
1713 1716
1728
1729:- if(exists_source(library(archive))). 1730ensure_loaded_archive :-
1731 current_predicate(archive_open/3),
1732 !.
1733ensure_loaded_archive :-
1734 use_module(library(archive)).
1735
1736pack_archive_info(Archive, Pack, [archive_size(Bytes)|Info], Strip) :-
1737 ensure_loaded_archive,
1738 size_file(Archive, Bytes),
1739 setup_call_cleanup(
1740 archive_open(Archive, Handle, []),
1741 ( repeat,
1742 ( archive_next_header(Handle, InfoFile)
1743 -> true
1744 ; !, fail
1745 )
1746 ),
1747 archive_close(Handle)),
1748 file_base_name(InfoFile, 'pack.pl'),
1749 atom_concat(Prefix, 'pack.pl', InfoFile),
1750 strip_option(Prefix, Pack, Strip),
1751 setup_call_cleanup(
1752 archive_open_entry(Handle, Stream),
1753 read_stream_to_terms(Stream, Info),
1754 close(Stream)),
1755 !,
1756 must_be(ground, Info),
1757 maplist(valid_term(pack_info_term), Info).
1758:- else. 1759pack_archive_info(_, _, _, _) :-
1760 existence_error(library, archive).
1761:- endif. 1762pack_archive_info(_, _, _, _) :-
1763 existence_error(pack_file, 'pack.pl').
1764
1765strip_option('', _, []) :- !.
1766strip_option('./', _, []) :- !.
1767strip_option(Prefix, Pack, [remove_prefix(Prefix)]) :-
1768 atom_concat(PrefixDir, /, Prefix),
1769 file_base_name(PrefixDir, Base),
1770 ( Base == Pack
1771 -> true
1772 ; pack_version_file(Pack, _, Base)
1773 -> true
1774 ; \+ sub_atom(PrefixDir, _, _, _, /)
1775 ).
1776
1777read_stream_to_terms(Stream, Terms) :-
1778 read(Stream, Term0),
1779 read_stream_to_terms(Term0, Stream, Terms).
1780
1781read_stream_to_terms(end_of_file, _, []) :- !.
1782read_stream_to_terms(Term0, Stream, [Term0|Terms]) :-
1783 read(Stream, Term1),
1784 read_stream_to_terms(Term1, Stream, Terms).
1785
1786
1791
1792pack_git_info(GitDir, Hash, [git(true), installed_size(Bytes)|Info]) :-
1793 exists_directory(GitDir),
1794 !,
1795 git_ls_tree(Entries, [directory(GitDir)]),
1796 git_hash(Hash, [directory(GitDir)]),
1797 maplist(arg(4), Entries, Sizes),
1798 sum_list(Sizes, Bytes),
1799 dir_metadata(GitDir, Info).
1800
1801dir_metadata(GitDir, Info) :-
1802 directory_file_path(GitDir, 'pack.pl', InfoFile),
1803 read_file_to_terms(InfoFile, Info, [encoding(utf8)]),
1804 must_be(ground, Info),
1805 maplist(valid_term(pack_info_term), Info).
1806
1810
1811download_file_sanity_check(Archive, Pack, Info) :-
1812 info_field(name(PackName), Info),
1813 info_field(version(PackVersion), Info),
1814 pack_version_file(PackFile, FileVersion, Archive),
1815 must_match([Pack, PackName, PackFile], name),
1816 must_match([PackVersion, FileVersion], version).
1817
1818info_field(Field, Info) :-
1819 memberchk(Field, Info),
1820 ground(Field),
1821 !.
1822info_field(Field, _Info) :-
1823 functor(Field, FieldName, _),
1824 print_message(error, pack(missing(FieldName))),
1825 fail.
1826
1827must_match(Values, _Field) :-
1828 sort(Values, [_]),
1829 !.
1830must_match(Values, Field) :-
1831 print_message(error, pack(conflict(Field, Values))),
1832 fail.
1833
1834
1835 1838
1850
1851prepare_pack_dir(Dir, Options) :-
1852 exists_directory(Dir),
1853 !,
1854 ( empty_directory(Dir)
1855 -> true
1856 ; remove_existing_pack(Dir, Options)
1857 -> make_directory(Dir)
1858 ).
1859prepare_pack_dir(Dir, _) :-
1860 ( read_link(Dir, _, _)
1861 ; access_file(Dir, exist)
1862 ),
1863 !,
1864 delete_file(Dir),
1865 make_directory(Dir).
1866prepare_pack_dir(Dir, _) :-
1867 make_directory(Dir).
1868
1872
1873empty_directory(Dir) :-
1874 \+ ( directory_files(Dir, Entries),
1875 member(Entry, Entries),
1876 \+ special(Entry)
1877 ).
1878
1879special(.).
1880special(..).
1881
1888
1889remove_existing_pack(PackDir, Options) :-
1890 exists_directory(PackDir),
1891 !,
1892 ( ( option(upgrade(true), Options)
1893 ; confirm(remove_existing_pack(PackDir), yes, Options)
1894 )
1895 -> delete_directory_and_contents(PackDir)
1896 ; print_message(error, pack(directory_exists(PackDir))),
1897 fail
1898 ).
1899remove_existing_pack(_, _).
1900
1914
1915pack_download_from_url(URL, PackTopDir, Pack, Options) :-
1916 option(git(true), Options),
1917 !,
1918 directory_file_path(PackTopDir, Pack, PackDir),
1919 prepare_pack_dir(PackDir, Options),
1920 ( option(branch(Branch), Options)
1921 -> Extra = ['--branch', Branch]
1922 ; Extra = []
1923 ),
1924 run_process(path(git), [clone, URL, PackDir|Extra], []),
1925 git_checkout_version(PackDir, [update(false)|Options]),
1926 option(pack_dir(PackDir), Options, _).
1927pack_download_from_url(URL, PackTopDir, Pack, Options) :-
1928 download_url(URL),
1929 !,
1930 directory_file_path(PackTopDir, Pack, PackDir),
1931 prepare_pack_dir(PackDir, Options),
1932 pack_download_dir(PackTopDir, DownLoadDir),
1933 download_file(URL, Pack, DownloadBase, Options),
1934 directory_file_path(DownLoadDir, DownloadBase, DownloadFile),
1935 ( option(insecure(true), Options, false)
1936 -> TLSOptions = [cert_verify_hook(ssl_verify)]
1937 ; TLSOptions = []
1938 ),
1939 print_message(informational, pack(download(begin, Pack, URL, DownloadFile))),
1940 setup_call_cleanup(
1941 http_open(URL, In, TLSOptions),
1942 setup_call_cleanup(
1943 open(DownloadFile, write, Out, [type(binary)]),
1944 copy_stream_data(In, Out),
1945 close(Out)),
1946 close(In)),
1947 print_message(informational, pack(download(end, Pack, URL, DownloadFile))),
1948 pack_archive_info(DownloadFile, Pack, Info, _),
1949 ( option(git_url(GitURL), Options)
1950 -> Origin = GitURL 1951 ; download_file_sanity_check(DownloadFile, Pack, Info),
1952 Origin = URL
1953 ),
1954 pack_unpack_from_local(DownloadFile, PackTopDir, Pack, PackDir, Options),
1955 pack_assert(PackDir, archive(DownloadFile, Origin)),
1956 option(pack_dir(PackDir), Options, _).
1957pack_download_from_url(URL, PackTopDir, Pack, Options) :-
1958 local_uri_file_name(URL, File),
1959 !,
1960 pack_unpack_from_local(File, PackTopDir, Pack, PackDir, Options),
1961 pack_assert(PackDir, archive(File, URL)),
1962 option(pack_dir(PackDir), Options, _).
1963pack_download_from_url(URL, _PackTopDir, _Pack, _Options) :-
1964 domain_error(url, URL).
1965
1987
1988git_checkout_version(PackDir, Options) :-
1989 option(commit('HEAD'), Options),
1990 option(branch(Branch), Options),
1991 !,
1992 git_ensure_on_branch(PackDir, Branch),
1993 run_process(path(git), ['-C', PackDir, pull], []).
1994git_checkout_version(PackDir, Options) :-
1995 option(commit('HEAD'), Options),
1996 git_current_branch(_, [directory(PackDir)]),
1997 !,
1998 run_process(path(git), ['-C', PackDir, pull], []).
1999git_checkout_version(PackDir, Options) :-
2000 option(commit('HEAD'), Options),
2001 !,
2002 git_default_branch(Branch, [directory(PackDir)]),
2003 git_ensure_on_branch(PackDir, Branch),
2004 run_process(path(git), ['-C', PackDir, pull], []).
2005git_checkout_version(PackDir, Options) :-
2006 option(commit(Hash), Options),
2007 run_process(path(git), ['-C', PackDir, fetch], []),
2008 git_branches(Branches, [contains(Hash), directory(PackDir)]),
2009 git_process_output(['-C', PackDir, 'rev-parse' | Branches],
2010 read_lines_to_atoms(Commits),
2011 []),
2012 nth1(I, Commits, Hash),
2013 nth1(I, Branches, Branch),
2014 !,
2015 git_ensure_on_branch(PackDir, Branch).
2016git_checkout_version(PackDir, Options) :-
2017 option(commit(Hash), Options),
2018 !,
2019 run_process(path(git), ['-C', PackDir, checkout, '--quiet', Hash], []).
2020git_checkout_version(PackDir, Options) :-
2021 option(version(Version), Options),
2022 !,
2023 git_tags(Tags, [directory(PackDir)]),
2024 ( memberchk(Version, Tags)
2025 -> Tag = Version
2026 ; member(Tag, Tags),
2027 sub_atom(Tag, B, _, 0, Version),
2028 sub_atom(Tag, 0, B, _, Prefix),
2029 version_prefix(Prefix)
2030 -> true
2031 ; existence_error(version_tag, Version)
2032 ),
2033 run_process(path(git), ['-C', PackDir, checkout, Tag], []).
2034git_checkout_version(_PackDir, Options) :-
2035 option(fresh(true), Options),
2036 !.
2037git_checkout_version(PackDir, _Options) :-
2038 git_current_branch(_, [directory(PackDir)]),
2039 !,
2040 run_process(path(git), ['-C', PackDir, pull], []).
2041git_checkout_version(PackDir, _Options) :-
2042 git_default_branch(Branch, [directory(PackDir)]),
2043 git_ensure_on_branch(PackDir, Branch),
2044 run_process(path(git), ['-C', PackDir, pull], []).
2045
2049
2050git_ensure_on_branch(PackDir, Branch) :-
2051 git_current_branch(Branch, [directory(PackDir)]),
2052 !.
2053git_ensure_on_branch(PackDir, Branch) :-
2054 run_process(path(git), ['-C', PackDir, checkout, Branch], []).
2055
2056read_lines_to_atoms(Atoms, In) :-
2057 read_line_to_string(In, Line),
2058 ( Line == end_of_file
2059 -> Atoms = []
2060 ; atom_string(Atom, Line),
2061 Atoms = [Atom|T],
2062 read_lines_to_atoms(T, In)
2063 ).
2064
2065version_prefix(Prefix) :-
2066 atom_codes(Prefix, Codes),
2067 phrase(version_prefix, Codes).
2068
2069version_prefix -->
2070 [C],
2071 { code_type(C, alpha) },
2072 !,
2073 version_prefix.
2074version_prefix -->
2075 "-".
2076version_prefix -->
2077 "_".
2078version_prefix -->
2079 "".
2080
2085
2086download_file(URL, Pack, File, Options) :-
2087 option(version(Version), Options),
2088 !,
2089 file_name_extension(_, Ext, URL),
2090 format(atom(File), '~w-~w.~w', [Pack, Version, Ext]).
2091download_file(URL, Pack, File, _) :-
2092 file_base_name(URL,Basename),
2093 no_int_file_name_extension(Tag,Ext,Basename),
2094 tag_version(Tag,Version),
2095 !,
2096 format(atom(File0), '~w-~w', [Pack, Version]),
2097 file_name_extension(File0, Ext, File).
2098download_file(URL, _, File, _) :-
2099 file_base_name(URL, File).
2100
2106
2107:- public pack_url_file/2. 2108pack_url_file(URL, FileID) :-
2109 github_release_url(URL, Pack, Version),
2110 !,
2111 download_file(URL, Pack, FileID, [version(Version)]).
2112pack_url_file(URL, FileID) :-
2113 file_base_name(URL, FileID).
2114
2119
2120:- public ssl_verify/5. 2121ssl_verify(_SSL,
2122 _ProblemCertificate, _AllCertificates, _FirstCertificate,
2123 _Error).
2124
2125pack_download_dir(PackTopDir, DownLoadDir) :-
2126 directory_file_path(PackTopDir, 'Downloads', DownLoadDir),
2127 ( exists_directory(DownLoadDir)
2128 -> true
2129 ; make_directory(DownLoadDir)
2130 ),
2131 ( access_file(DownLoadDir, write)
2132 -> true
2133 ; permission_error(write, directory, DownLoadDir)
2134 ).
2135
2141
2142download_url(URL) :-
2143 atom(URL),
2144 uri_components(URL, Components),
2145 uri_data(scheme, Components, Scheme),
2146 download_scheme(Scheme).
2147
2148download_scheme(http).
2149download_scheme(https).
2150
2158
2159pack_post_install(Pack, PackDir, Options) :-
2160 post_install_foreign(Pack, PackDir, Options),
2161 post_install_autoload(PackDir, Options),
2162 attach_packs(PackDir, [duplicate(warning)]).
2163
2169
2170pack_rebuild :-
2171 forall(current_pack(Pack),
2172 ( print_message(informational, pack(rebuild(Pack))),
2173 pack_rebuild(Pack)
2174 )).
2175
2176pack_rebuild(Pack) :-
2177 current_pack(Pack, PackDir),
2178 !,
2179 post_install_foreign(Pack, PackDir, [rebuild(true)]).
2180pack_rebuild(Pack) :-
2181 unattached_pack(Pack, PackDir),
2182 !,
2183 post_install_foreign(Pack, PackDir, [rebuild(true)]).
2184pack_rebuild(Pack) :-
2185 existence_error(pack, Pack).
2186
2187unattached_pack(Pack, BaseDir) :-
2188 directory_file_path(Pack, 'pack.pl', PackFile),
2189 absolute_file_name(pack(PackFile), PackPath,
2190 [ access(read),
2191 file_errors(fail)
2192 ]),
2193 file_directory_name(PackPath, BaseDir).
2194
2195
2196
2208
2209post_install_foreign(Pack, PackDir, Options) :-
2210 is_foreign_pack(PackDir, _),
2211 !,
2212 ( pack_info_term(PackDir, pack_version(Version))
2213 -> true
2214 ; Version = 1
2215 ),
2216 option(rebuild(Rebuild), Options, if_absent),
2217 current_prolog_flag(arch, Arch),
2218 prolog_version_dotted(PrologVersion),
2219 ( Rebuild == if_absent,
2220 foreign_present(PackDir, Arch)
2221 -> print_message(informational, pack(kept_foreign(Pack, Arch))),
2222 ( pack_status_dir(PackDir, built(Arch, _, _))
2223 -> true
2224 ; pack_assert(PackDir, built(Arch, PrologVersion, downloaded))
2225 )
2226 ; BuildSteps0 = [[dependencies], [configure], build, install, [test]],
2227 ( Rebuild == true
2228 -> BuildSteps1 = [distclean|BuildSteps0]
2229 ; BuildSteps1 = BuildSteps0
2230 ),
2231 ( option(test(false), Options)
2232 -> delete(BuildSteps1, [test], BuildSteps2)
2233 ; BuildSteps2 = BuildSteps1
2234 ),
2235 ( option(clean(true), Options)
2236 -> append(BuildSteps2, [[clean]], BuildSteps)
2237 ; BuildSteps = BuildSteps2
2238 ),
2239 build_steps(BuildSteps, PackDir, [pack_version(Version)|Options]),
2240 pack_assert(PackDir, built(Arch, PrologVersion, built))
2241 ).
2242post_install_foreign(_, _, _).
2243
2244
2252
2253foreign_present(PackDir, Arch) :-
2254 atomic_list_concat([PackDir, '/lib'], ForeignBaseDir),
2255 exists_directory(ForeignBaseDir),
2256 !,
2257 atomic_list_concat([PackDir, '/lib/', Arch], ForeignDir),
2258 exists_directory(ForeignDir),
2259 current_prolog_flag(shared_object_extension, Ext),
2260 atomic_list_concat([ForeignDir, '/*.', Ext], Pattern),
2261 expand_file_name(Pattern, Files),
2262 Files \== [].
2263
2268
2269is_foreign_pack(PackDir, Type) :-
2270 foreign_file(File, Type),
2271 directory_file_path(PackDir, File, Path),
2272 exists_file(Path).
2273
2274foreign_file('CMakeLists.txt', cmake).
2275foreign_file('configure', configure).
2276foreign_file('configure.in', autoconf).
2277foreign_file('configure.ac', autoconf).
2278foreign_file('Makefile.am', automake).
2279foreign_file('Makefile', make).
2280foreign_file('makefile', make).
2281foreign_file('conanfile.txt', conan).
2282foreign_file('conanfile.py', conan).
2283
2284
2285 2288
2292
2293post_install_autoload(PackDir, Options) :-
2294 is_autoload_pack(PackDir, Options),
2295 !,
2296 directory_file_path(PackDir, prolog, PrologLibDir),
2297 make_library_index(PrologLibDir).
2298post_install_autoload(_, _).
2299
2300is_autoload_pack(PackDir, Options) :-
2301 option(autoload(true), Options, true),
2302 pack_info_term(PackDir, autoload(true)).
2303
2304
2305 2308
2312
2313pack_upgrade(Pack) :-
2314 pack_install(Pack, [upgrade(true)]).
2315
2316
2317 2320
2331
2332pack_remove(Pack) :-
2333 pack_remove(Pack, []).
2334
2335pack_remove(Pack, Options) :-
2336 option(dependencies(false), Options),
2337 !,
2338 pack_remove_forced(Pack).
2339pack_remove(Pack, Options) :-
2340 ( dependents(Pack, Deps)
2341 -> ( option(dependencies(true), Options)
2342 -> true
2343 ; confirm_remove(Pack, Deps, Delete, Options)
2344 ),
2345 forall(member(P, Delete), pack_remove_forced(P))
2346 ; pack_remove_forced(Pack)
2347 ).
2348
2349pack_remove_forced(Pack) :-
2350 catch('$pack_detach'(Pack, BaseDir),
2351 error(existence_error(pack, Pack), _),
2352 fail),
2353 !,
2354 print_message(informational, pack(remove(BaseDir))),
2355 delete_directory_and_contents(BaseDir).
2356pack_remove_forced(Pack) :-
2357 unattached_pack(Pack, BaseDir),
2358 !,
2359 delete_directory_and_contents(BaseDir).
2360pack_remove_forced(Pack) :-
2361 print_message(informational, error(existence_error(pack, Pack),_)).
2362
2363confirm_remove(Pack, Deps, Delete, Options) :-
2364 print_message(warning, pack(depends(Pack, Deps))),
2365 menu(pack(resolve_remove),
2366 [ [Pack] = remove_only(Pack),
2367 [Pack|Deps] = remove_deps(Pack, Deps),
2368 [] = cancel
2369 ], [], Delete, Options),
2370 Delete \== [].
2371
2372
2373 2376
2427
2428pack_publish(Dir, Options) :-
2429 \+ download_url(Dir),
2430 is_git_directory(Dir), !,
2431 pack_git_info(Dir, _Hash, Metadata),
2432 prepare_repository(Dir, Metadata, Options),
2433 ( memberchk(download(URL), Metadata),
2434 git_url(URL, _)
2435 -> true
2436 ; option(remote(Remote), Options, origin),
2437 git_remote_url(Remote, RemoteURL, [directory(Dir)]),
2438 git_to_https_url(RemoteURL, URL)
2439 ),
2440 memberchk(version(Version), Metadata),
2441 pack_publish_(URL,
2442 [ version(Version)
2443 | Options
2444 ]).
2445pack_publish(Spec, Options) :-
2446 pack_publish_(Spec, Options).
2447
2448pack_publish_(Spec, Options) :-
2449 pack_default_options(Spec, Pack, Options, DefOptions),
2450 option(url(URL), DefOptions),
2451 valid_publish_url(URL, Options),
2452 prepare_build_location(Pack, Dir, Clean, Options),
2453 ( option(register(false), Options)
2454 -> InstallOptions = DefOptions
2455 ; InstallOptions = [publish(Pack)|DefOptions]
2456 ),
2457 call_cleanup(pack_install(Pack,
2458 [ pack(Pack)
2459 | InstallOptions
2460 ]),
2461 cleanup_publish(Clean, Dir)).
2462
2463cleanup_publish(true, Dir) :-
2464 !,
2465 delete_directory_and_contents(Dir).
2466cleanup_publish(_, _).
2467
2468valid_publish_url(URL, Options) :-
2469 option(register(Register), Options, true),
2470 ( Register == false
2471 -> true
2472 ; download_url(URL)
2473 -> true
2474 ; permission_error(publish, pack, URL)
2475 ).
2476
2477prepare_build_location(Pack, Dir, Clean, Options) :-
2478 ( option(pack_directory(Dir), Options)
2479 -> ensure_directory(Dir),
2480 ( option(clean(true), Options, true)
2481 -> delete_directory_contents(Dir)
2482 ; true
2483 )
2484 ; tmp_file(pack, Dir),
2485 make_directory(Dir),
2486 Clean = true
2487 ),
2488 ( option(isolated(false), Options)
2489 -> detach_pack(Pack, _),
2490 attach_packs(Dir, [search(first)])
2491 ; attach_packs(Dir, [replace(true)])
2492 ).
2493
2494
2495
2502
2503prepare_repository(_Dir, _Metadata, Options) :-
2504 option(register(false), Options),
2505 !.
2506prepare_repository(Dir, Metadata, Options) :-
2507 git_dir_must_be_clean(Dir),
2508 git_must_be_on_default_branch(Dir, Options),
2509 tag_git_dir(Dir, Metadata, Action, Options),
2510 confirm(git_push, yes, Options),
2511 run_process(path(git), ['-C', file(Dir), push ], []),
2512 ( Action = push_tag(Tag)
2513 -> run_process(path(git), ['-C', file(Dir), push, origin, Tag ], [])
2514 ; true
2515 ).
2516
2517git_dir_must_be_clean(Dir) :-
2518 git_describe(Description, [directory(Dir)]),
2519 ( sub_atom(Description, _, _, 0, '-DIRTY')
2520 -> print_message(error, pack(git_not_clean(Dir))),
2521 fail
2522 ; true
2523 ).
2524
2525git_must_be_on_default_branch(Dir, Options) :-
2526 ( option(branch(Default), Options)
2527 -> true
2528 ; git_default_branch(Default, [directory(Dir)])
2529 ),
2530 git_current_branch(Current, [directory(Dir)]),
2531 ( Default == Current
2532 -> true
2533 ; print_message(error,
2534 pack(git_branch_not_default(Dir, Default, Current))),
2535 fail
2536 ).
2537
2538
2544
2545tag_git_dir(Dir, Metadata, Action, Options) :-
2546 memberchk(version(Version), Metadata),
2547 atom_concat('V', Version, Tag),
2548 git_tags(Tags, [directory(Dir)]),
2549 ( memberchk(Tag, Tags)
2550 -> git_tag_is_consistent(Dir, Tag, Action, Options)
2551 ; format(string(Message), 'Release ~w', [Version]),
2552 findall(Opt, git_tag_option(Opt, Options), Argv,
2553 [ '-m', Message, Tag ]),
2554 confirm(git_tag(Tag), yes, Options),
2555 run_process(path(git), ['-C', file(Dir), tag | Argv ], []),
2556 Action = push_tag(Tag)
2557 ).
2558
2559git_tag_option('-s', Options) :- option(sign(true), Options, true).
2560git_tag_option('-f', Options) :- option(force(true), Options, true).
2561
2562git_tag_is_consistent(Dir, Tag, Action, Options) :-
2563 format(atom(TagRef), 'refs/tags/~w', [Tag]),
2564 format(atom(CommitRef), 'refs/tags/~w^{}', [Tag]),
2565 option(remote(Remote), Options, origin),
2566 git_ls_remote(Dir, LocalTags, [tags(true)]),
2567 memberchk(CommitHash-CommitRef, LocalTags),
2568 ( git_hash(CommitHash, [directory(Dir)])
2569 -> true
2570 ; print_message(error, pack(git_release_tag_not_at_head(Tag))),
2571 fail
2572 ),
2573 memberchk(TagHash-TagRef, LocalTags),
2574 git_ls_remote(Remote, RemoteTags, [tags(true)]),
2575 ( memberchk(RemoteCommitHash-CommitRef, RemoteTags),
2576 memberchk(RemoteTagHash-TagRef, RemoteTags)
2577 -> ( RemoteCommitHash == CommitHash,
2578 RemoteTagHash == TagHash
2579 -> Action = none
2580 ; print_message(error, pack(git_tag_out_of_sync(Tag))),
2581 fail
2582 )
2583 ; Action = push_tag(Tag)
2584 ).
2585
2591
2592git_to_https_url(URL, URL) :-
2593 download_url(URL),
2594 !.
2595git_to_https_url(GitURL, URL) :-
2596 atom_concat('git@github.com:', Repo, GitURL),
2597 !,
2598 atom_concat('https://github.com/', Repo, URL).
2599git_to_https_url(GitURL, _) :-
2600 print_message(error, pack(git_no_https(GitURL))),
2601 fail.
2602
2603
2604 2607
2628
2629pack_property(Pack, Property) :-
2630 findall(Pack-Property, pack_property_(Pack, Property), List),
2631 member(Pack-Property, List). 2632
2633pack_property_(Pack, Property) :-
2634 pack_info(Pack, _, Property).
2635pack_property_(Pack, Property) :-
2636 \+ \+ info_file(Property, _),
2637 '$pack':pack(Pack, BaseDir),
2638 access_file(BaseDir, read),
2639 directory_files(BaseDir, Files),
2640 member(File, Files),
2641 info_file(Property, Pattern),
2642 downcase_atom(File, Pattern),
2643 directory_file_path(BaseDir, File, InfoFile),
2644 arg(1, Property, InfoFile).
2645
2646info_file(readme(_), 'readme.txt').
2647info_file(readme(_), 'readme').
2648info_file(todo(_), 'todo.txt').
2649info_file(todo(_), 'todo').
2650
2651
2652 2655
2662
2663pack_version_file(Pack, Version, GitHubRelease) :-
2664 atomic(GitHubRelease),
2665 github_release_url(GitHubRelease, Pack, Version),
2666 !.
2667pack_version_file(Pack, Version, Path) :-
2668 atomic(Path),
2669 file_base_name(Path, File),
2670 no_int_file_name_extension(Base, _Ext, File),
2671 atom_codes(Base, Codes),
2672 ( phrase(pack_version(Pack, Version), Codes),
2673 safe_pack_name(Pack)
2674 -> true
2675 ).
2676
2677no_int_file_name_extension(Base, Ext, File) :-
2678 file_name_extension(Base0, Ext0, File),
2679 \+ atom_number(Ext0, _),
2680 !,
2681 Base = Base0,
2682 Ext = Ext0.
2683no_int_file_name_extension(File, '', File).
2684
2689
2690safe_pack_name(Name) :-
2691 atom_length(Name, Len),
2692 Len >= 3, 2693 atom_codes(Name, Codes),
2694 maplist(safe_pack_char, Codes),
2695 !.
2696
2697safe_pack_char(C) :- between(0'a, 0'z, C), !.
2698safe_pack_char(C) :- between(0'A, 0'Z, C), !.
2699safe_pack_char(C) :- between(0'0, 0'9, C), !.
2700safe_pack_char(0'_).
2701
2705
2706pack_version(Pack, Version) -->
2707 string(Codes), "-",
2708 version(Parts),
2709 !,
2710 { atom_codes(Pack, Codes),
2711 atomic_list_concat(Parts, '.', Version)
2712 }.
2713
2714version([H|T]) -->
2715 version_part(H),
2716 ( "."
2717 -> version(T)
2718 ; {T=[]}
2719 ).
2720
2721version_part(*) --> "*", !.
2722version_part(Int) --> integer(Int).
2723
2724
2725 2728
2729have_git :-
2730 process_which(path(git), _).
2731
2732
2736
2737git_url(URL, Pack) :-
2738 uri_components(URL, Components),
2739 uri_data(scheme, Components, Scheme),
2740 nonvar(Scheme), 2741 uri_data(path, Components, Path),
2742 ( Scheme == git
2743 -> true
2744 ; git_download_scheme(Scheme),
2745 file_name_extension(_, git, Path)
2746 ; git_download_scheme(Scheme),
2747 catch(git_ls_remote(URL, _, [refs(['HEAD']), error(_)]), _, fail)
2748 -> true
2749 ),
2750 file_base_name(Path, PackExt),
2751 ( file_name_extension(Pack, git, PackExt)
2752 -> true
2753 ; Pack = PackExt
2754 ),
2755 ( safe_pack_name(Pack)
2756 -> true
2757 ; domain_error(pack_name, Pack)
2758 ).
2759
2760git_download_scheme(http).
2761git_download_scheme(https).
2762
2769
2770github_release_url(URL, Pack, Version) :-
2771 uri_components(URL, Components),
2772 uri_data(authority, Components, 'github.com'),
2773 uri_data(scheme, Components, Scheme),
2774 download_scheme(Scheme),
2775 uri_data(path, Components, Path),
2776 github_archive_path(Archive,Pack,File),
2777 atomic_list_concat(Archive, /, Path),
2778 file_name_extension(Tag, Ext, File),
2779 github_archive_extension(Ext),
2780 tag_version(Tag, Version),
2781 !.
2782
2783github_archive_path(['',_User,Pack,archive,File],Pack,File).
2784github_archive_path(['',_User,Pack,archive,refs,tags,File],Pack,File).
2785
2786github_archive_extension(tgz).
2787github_archive_extension(zip).
2788
2793
2794tag_version(Tag, Version) :-
2795 version_tag_prefix(Prefix),
2796 atom_concat(Prefix, Version, Tag),
2797 is_version(Version).
2798
2799version_tag_prefix(v).
2800version_tag_prefix('V').
2801version_tag_prefix('').
2802
2803
2809
2810git_archive_url(URL, Archive, Options) :-
2811 uri_components(URL, Components),
2812 uri_data(authority, Components, 'github.com'),
2813 uri_data(path, Components, Path),
2814 atomic_list_concat(['', User, RepoGit], /, Path),
2815 $,
2816 remove_git_ext(RepoGit, Repo),
2817 git_archive_version(Version, Options),
2818 atomic_list_concat(['', User, Repo, zip, Version], /, ArchivePath),
2819 uri_edit([ path(ArchivePath),
2820 host('codeload.github.com')
2821 ],
2822 URL, Archive).
2823git_archive_url(URL, _, _) :-
2824 print_message(error, pack(no_git(URL))),
2825 fail.
2826
2827remove_git_ext(RepoGit, Repo) :-
2828 file_name_extension(Repo, git, RepoGit),
2829 !.
2830remove_git_ext(Repo, Repo).
2831
2832git_archive_version(Version, Options) :-
2833 option(commit(Version), Options),
2834 !.
2835git_archive_version(Version, Options) :-
2836 option(branch(Version), Options),
2837 !.
2838git_archive_version(Version, Options) :-
2839 option(version(Version), Options),
2840 !.
2841git_archive_version('HEAD', _).
2842
2843 2846
2851
2852register_downloads(_, Options) :-
2853 option(register(false), Options),
2854 \+ option(do_publish(_), Options),
2855 !.
2856register_downloads(Infos, Options) :-
2857 convlist(download_data, Infos, Data),
2858 ( Data == []
2859 -> true
2860 ; query_pack_server(downloaded(Data), Reply, Options),
2861 ( option(do_publish(Pack), Options)
2862 -> ( member(Info, Infos),
2863 Info.pack == Pack
2864 -> true
2865 ),
2866 ( Reply = true(Actions),
2867 memberchk(Pack-Result, Actions)
2868 -> ( registered(Result)
2869 -> true
2870 ; print_message(error, pack(publish_failed(Info, Result))),
2871 fail
2872 )
2873 ; print_message(error, pack(publish_failed(Info, false)))
2874 )
2875 ; true
2876 )
2877 ).
2878
2879registered(git(_URL)).
2880registered(file(_URL)).
2881
2882publish_download(Infos, Options) :-
2883 select_option(publish(Pack), Options, Options1),
2884 !,
2885 register_downloads(Infos, [do_publish(Pack)|Options1]).
2886publish_download(_Infos, _Options).
2887
2888download_data(Info, Data),
2889 Info.get(git) == true => 2890 Data = download(URL, Hash, Metadata),
2891 URL = Info.get(downloaded),
2892 pack_git_info(Info.installed, Hash, Metadata).
2893download_data(Info, Data),
2894 _{git_url:URL,hash:Hash} :< Info, Hash \== (-) =>
2895 Data = download(URL, Hash, Metadata), 2896 dir_metadata(Info.installed, Metadata).
2897download_data(Info, Data) => 2898 Data = download(URL, Hash, Metadata),
2899 URL = Info.get(downloaded),
2900 download_url(URL),
2901 pack_status_dir(Info.installed, archive(Archive, URL)),
2902 file_sha1(Archive, Hash),
2903 pack_archive_info(Archive, _Pack, Metadata, _).
2904
2909
2910query_pack_server(Query, Result, Options) :-
2911 ( option(server(ServerOpt), Options)
2912 -> server_url(ServerOpt, ServerBase)
2913 ; setting(server, ServerBase),
2914 ServerBase \== ''
2915 ),
2916 atom_concat(ServerBase, query, Server),
2917 format(codes(Data), '~q.~n', Query),
2918 info_level(Informational, Options),
2919 print_message(Informational, pack(contacting_server(Server))),
2920 setup_call_cleanup(
2921 http_open(Server, In,
2922 [ post(codes(application/'x-prolog', Data)),
2923 header(content_type, ContentType)
2924 ]),
2925 read_reply(ContentType, In, Result),
2926 close(In)),
2927 message_severity(Result, Level, Informational),
2928 print_message(Level, pack(server_reply(Result))).
2929
2930server_url(URL0, URL) :-
2931 uri_components(URL0, Components),
2932 uri_data(scheme, Components, Scheme),
2933 var(Scheme),
2934 !,
2935 atom_concat('https://', URL0, URL1),
2936 server_url(URL1, URL).
2937server_url(URL0, URL) :-
2938 uri_components(URL0, Components),
2939 uri_data(path, Components, ''),
2940 !,
2941 uri_edit([path('/pack/')], URL0, URL).
2942server_url(URL, URL).
2943
2944read_reply(ContentType, In, Result) :-
2945 sub_atom(ContentType, 0, _, _, 'application/x-prolog'),
2946 !,
2947 set_stream(In, encoding(utf8)),
2948 read(In, Result).
2949read_reply(ContentType, In, _Result) :-
2950 read_string(In, 500, String),
2951 print_message(error, pack(no_prolog_response(ContentType, String))),
2952 fail.
2953
2954info_level(Level, Options) :-
2955 option(silent(true), Options),
2956 !,
2957 Level = silent.
2958info_level(informational, _).
2959
2960message_severity(true(_), Informational, Informational).
2961message_severity(false, warning, _).
2962message_severity(exception(_), error, _).
2963
2964
2965 2968
2975
2976available_download_versions(URL, Versions) :-
2977 wildcard_pattern(URL),
2978 github_url(URL, User, Repo),
2979 !,
2980 findall(Version-VersionURL,
2981 github_version(User, Repo, Version, VersionURL),
2982 Versions).
2983available_download_versions(URL, Versions) :-
2984 wildcard_pattern(URL),
2985 !,
2986 file_directory_name(URL, DirURL0),
2987 ensure_slash(DirURL0, DirURL),
2988 print_message(informational, pack(query_versions(DirURL))),
2989 setup_call_cleanup(
2990 http_open(DirURL, In, []),
2991 load_html(stream(In), DOM,
2992 [ syntax_errors(quiet)
2993 ]),
2994 close(In)),
2995 findall(MatchingURL,
2996 absolute_matching_href(DOM, URL, MatchingURL),
2997 MatchingURLs),
2998 ( MatchingURLs == []
2999 -> print_message(warning, pack(no_matching_urls(URL)))
3000 ; true
3001 ),
3002 versioned_urls(MatchingURLs, VersionedURLs),
3003 sort_version_pairs(VersionedURLs, Versions),
3004 print_message(informational, pack(found_versions(Versions))).
3005available_download_versions(URL, [Version-URL]) :-
3006 ( pack_version_file(_Pack, Version0, URL)
3007 -> Version = Version0
3008 ; Version = '0.0.0'
3009 ).
3010
3014
3015sort_version_pairs(Pairs, Sorted) :-
3016 map_list_to_pairs(version_pair_sort_key_, Pairs, Keyed),
3017 sort(1, @>=, Keyed, SortedKeyed),
3018 pairs_values(SortedKeyed, Sorted).
3019
3020version_pair_sort_key_(Version-_Data, Key) :-
3021 version_sort_key(Version, Key).
3022
3023version_sort_key(Version, Key) :-
3024 split_string(Version, ".", "", Parts),
3025 maplist(number_string, Key, Parts),
3026 !.
3027version_sort_key(Version, _) :-
3028 domain_error(version, Version).
3029
3033
3034github_url(URL, User, Repo) :-
3035 uri_components(URL, uri_components(https,'github.com',Path,_,_)),
3036 atomic_list_concat(['',User,Repo|_], /, Path).
3037
3038
3043
3044github_version(User, Repo, Version, VersionURI) :-
3045 atomic_list_concat(['',repos,User,Repo,tags], /, Path1),
3046 uri_components(ApiUri, uri_components(https,'api.github.com',Path1,_,_)),
3047 setup_call_cleanup(
3048 http_open(ApiUri, In,
3049 [ request_header('Accept'='application/vnd.github.v3+json')
3050 ]),
3051 json_read_dict(In, Dicts),
3052 close(In)),
3053 member(Dict, Dicts),
3054 atom_string(Tag, Dict.name),
3055 tag_version(Tag, Version),
3056 atom_string(VersionURI, Dict.zipball_url).
3057
3058wildcard_pattern(URL) :- sub_atom(URL, _, _, _, *).
3059wildcard_pattern(URL) :- sub_atom(URL, _, _, _, ?).
3060
3061ensure_slash(Dir, DirS) :-
3062 ( sub_atom(Dir, _, _, 0, /)
3063 -> DirS = Dir
3064 ; atom_concat(Dir, /, DirS)
3065 ).
3066
3067remove_slash(Dir0, Dir) :-
3068 Dir0 \== '/',
3069 atom_concat(Dir1, /, Dir0),
3070 !,
3071 remove_slash(Dir1, Dir).
3072remove_slash(Dir, Dir).
3073
3074absolute_matching_href(DOM, Pattern, Match) :-
3075 xpath(DOM, //a(@href), HREF),
3076 uri_normalized(HREF, Pattern, Match),
3077 wildcard_match(Pattern, Match).
3078
3079versioned_urls([], []).
3080versioned_urls([H|T0], List) :-
3081 file_base_name(H, File),
3082 ( pack_version_file(_Pack, Version, File)
3083 -> List = [Version-H|T]
3084 ; List = T
3085 ),
3086 versioned_urls(T0, T).
3087
3088
3089 3092
3098
3099pack_provides(Pack, Pack@Version) :-
3100 current_pack(Pack),
3101 once(pack_info(Pack, version, version(Version))).
3102pack_provides(Pack, Provides) :-
3103 findall(Prv, pack_info(Pack, dependency, provides(Prv)), PrvList),
3104 member(Provides, PrvList).
3105
3106pack_requires(Pack, Requires) :-
3107 current_pack(Pack),
3108 findall(Req, pack_info(Pack, dependency, requires(Req)), ReqList),
3109 member(Requires, ReqList).
3110
3111pack_conflicts(Pack, Conflicts) :-
3112 current_pack(Pack),
3113 findall(Cfl, pack_info(Pack, dependency, conflicts(Cfl)), CflList),
3114 member(Conflicts, CflList).
3115
3120
3121pack_depends_on(Pack, Dependency) :-
3122 ground(Pack),
3123 !,
3124 pack_requires(Pack, Requires),
3125 \+ is_prolog_token(Requires),
3126 pack_provides(Dependency, Provides),
3127 satisfies_req(Provides, Requires).
3128pack_depends_on(Pack, Dependency) :-
3129 ground(Dependency),
3130 !,
3131 pack_provides(Dependency, Provides),
3132 pack_requires(Pack, Requires),
3133 satisfies_req(Provides, Requires).
3134pack_depends_on(Pack, Dependency) :-
3135 current_pack(Pack),
3136 pack_depends_on(Pack, Dependency).
3137
3142
3143dependents(Pack, Deps) :-
3144 setof(Dep, dependent(Pack, Dep, []), Deps).
3145
3146dependent(Pack, Dep, Seen) :-
3147 pack_depends_on(Dep0, Pack),
3148 \+ memberchk(Dep0, Seen),
3149 ( Dep = Dep0
3150 ; dependent(Dep0, Dep, [Dep0|Seen])
3151 ).
3152
3156
3157validate_dependencies :-
3158 setof(Issue, pack_dependency_issue(_, Issue), Issues),
3159 !,
3160 print_message(warning, pack(dependency_issues(Issues))).
3161validate_dependencies.
3162
3172
3173pack_dependency_issue(Pack, Issue) :-
3174 current_pack(Pack),
3175 pack_dependency_issue_(Pack, Issue).
3176
3177pack_dependency_issue_(Pack, unsatisfied(Pack, Requires)) :-
3178 pack_requires(Pack, Requires),
3179 ( is_prolog_token(Requires)
3180 -> \+ prolog_satisfies(Requires)
3181 ; \+ ( pack_provides(_, Provides),
3182 satisfies_req(Provides, Requires) )
3183 ).
3184pack_dependency_issue_(Pack, conflicts(Pack, Conflicts)) :-
3185 pack_conflicts(Pack, Conflicts),
3186 ( is_prolog_token(Conflicts)
3187 -> prolog_satisfies(Conflicts)
3188 ; pack_provides(_, Provides),
3189 satisfies_req(Provides, Conflicts)
3190 ).
3191
3192
3193 3196
3210
3211pack_assert(PackDir, Fact) :-
3212 must_be(ground, Fact),
3213 findall(Term, pack_status_dir(PackDir, Term), Facts0),
3214 update_facts(Facts0, Fact, Facts),
3215 OpenOptions = [encoding(utf8), lock(exclusive)],
3216 status_file(PackDir, StatusFile),
3217 ( Facts == Facts0
3218 -> true
3219 ; Facts0 \== [],
3220 append(Facts0, New, Facts)
3221 -> setup_call_cleanup(
3222 open(StatusFile, append, Out, OpenOptions),
3223 maplist(write_fact(Out), New),
3224 close(Out))
3225 ; setup_call_cleanup(
3226 open(StatusFile, write, Out, OpenOptions),
3227 ( write_facts_header(Out),
3228 maplist(write_fact(Out), Facts)
3229 ),
3230 close(Out))
3231 ).
3232
3233update_facts([], Fact, [Fact]) :-
3234 !.
3235update_facts([H|T], Fact, [Fact|T]) :-
3236 general_pack_fact(Fact, GenFact),
3237 general_pack_fact(H, GenTerm),
3238 GenFact =@= GenTerm,
3239 !.
3240update_facts([H|T0], Fact, [H|T]) :-
3241 update_facts(T0, Fact, T).
3242
3243general_pack_fact(built(Arch, _Version, _How), General) =>
3244 General = built(Arch, _, _).
3245general_pack_fact(Term, General), compound(Term) =>
3246 compound_name_arity(Term, Name, Arity),
3247 compound_name_arity(General, Name, Arity).
3248general_pack_fact(Term, General) =>
3249 General = Term.
3250
(Out) :-
3252 format(Out, '% Fact status file. Managed by package manager.~n', []).
3253
3254write_fact(Out, Term) :-
3255 format(Out, '~q.~n', [Term]).
3256
3262
3263pack_status(Pack, Fact) :-
3264 current_pack(Pack, PackDir),
3265 pack_status_dir(PackDir, Fact).
3266
3267pack_status_dir(PackDir, Fact) :-
3268 det_if(ground(Fact), pack_status_(PackDir, Fact)).
3269
3270pack_status_(PackDir, Fact) :-
3271 status_file(PackDir, StatusFile),
3272 catch(term_in_file(valid_term(pack_status_term), StatusFile, Fact),
3273 error(existence_error(source_sink, StatusFile), _),
3274 fail).
3275
3276pack_status_term(built(atom, version, oneof([built,downloaded]))).
3277pack_status_term(automatic(boolean)).
3278pack_status_term(archive(atom, atom)).
3279
3280
3287
3288update_automatic(Info) :-
3289 _ = Info.get(dependency_for),
3290 \+ pack_status(Info.installed, automatic(_)),
3291 !,
3292 pack_assert(Info.installed, automatic(true)).
3293update_automatic(Info) :-
3294 pack_assert(Info.installed, automatic(false)).
3295
3296status_file(PackDir, StatusFile) :-
3297 directory_file_path(PackDir, 'status.db', StatusFile).
3298
3299 3302
3303:- multifile prolog:message//1. 3304
3306
(_Question, _Alternatives, Default, Selection, Options) :-
3308 option(interactive(false), Options),
3309 !,
3310 Selection = Default.
3311menu(Question, Alternatives, Default, Selection, _) :-
3312 length(Alternatives, N),
3313 between(1, 5, _),
3314 print_message(query, Question),
3315 print_menu(Alternatives, Default, 1),
3316 print_message(query, pack(menu(select))),
3317 read_selection(N, Choice),
3318 !,
3319 ( Choice == default
3320 -> Selection = Default
3321 ; nth1(Choice, Alternatives, Selection=_)
3322 -> true
3323 ).
3324
([], _, _).
3326print_menu([Value=Label|T], Default, I) :-
3327 ( Value == Default
3328 -> print_message(query, pack(menu(default_item(I, Label))))
3329 ; print_message(query, pack(menu(item(I, Label))))
3330 ),
3331 I2 is I + 1,
3332 print_menu(T, Default, I2).
3333
3334read_selection(Max, Choice) :-
3335 get_single_char(Code),
3336 ( answered_default(Code)
3337 -> Choice = default
3338 ; code_type(Code, digit(Choice)),
3339 between(1, Max, Choice)
3340 -> true
3341 ; print_message(warning, pack(menu(reply(1,Max)))),
3342 fail
3343 ).
3344
3350
3351confirm(_Question, Default, Options) :-
3352 Default \== none,
3353 option(interactive(false), Options, true),
3354 !,
3355 Default == yes.
3356confirm(Question, Default, _) :-
3357 between(1, 5, _),
3358 print_message(query, pack(confirm(Question, Default))),
3359 read_yes_no(YesNo, Default),
3360 !,
3361 format(user_error, '~N', []),
3362 YesNo == yes.
3363
3364read_yes_no(YesNo, Default) :-
3365 get_single_char(Code),
3366 code_yes_no(Code, Default, YesNo),
3367 !.
3368
3369code_yes_no(0'y, _, yes).
3370code_yes_no(0'Y, _, yes).
3371code_yes_no(0'n, _, no).
3372code_yes_no(0'N, _, no).
3373code_yes_no(_, none, _) :- !, fail.
3374code_yes_no(C, Default, Default) :-
3375 answered_default(C).
3376
3377answered_default(0'\r).
3378answered_default(0'\n).
3379answered_default(0'\s).
3380
3381
3382 3385
3386:- multifile prolog:message//1. 3387
3388prolog:message(pack(Message)) -->
3389 message(Message).
3390
3391:- discontiguous
3392 message//1,
3393 label//1. 3394
3395message(invalid_term(pack_info_term, Term)) -->
3396 [ 'Invalid package meta data: ~q'-[Term] ].
3397message(invalid_term(pack_status_term, Term)) -->
3398 [ 'Invalid package status data: ~q'-[Term] ].
3399message(directory_exists(Dir)) -->
3400 [ 'Package target directory exists and is not empty:', nl,
3401 '\t~q'-[Dir]
3402 ].
3403message(already_installed(pack(Pack, Version))) -->
3404 [ 'Pack `~w'' is already installed @~w'-[Pack, Version] ].
3405message(already_installed(Pack)) -->
3406 [ 'Pack `~w'' is already installed. Package info:'-[Pack] ].
3407message(kept_foreign(Pack, Arch)) -->
3408 [ 'Found foreign libraries for architecture '-[],
3409 ansi(code, '~q', [Arch]), nl,
3410 'Use ', ansi(code, '?- pack_rebuild(~q).', [Pack]),
3411 ' to rebuild from sources'-[]
3412 ].
3413message(no_pack_installed(Pack)) -->
3414 [ 'No pack ~q installed. Use ?- pack_list(Pattern) to search'-[Pack] ].
3415message(dependency_issues(Issues)) -->
3416 [ 'The current set of packs has dependency issues:', nl ],
3417 dep_issues(Issues).
3418message(depends(Pack, Deps)) -->
3419 [ 'The following packs depend on `~w\':'-[Pack], nl ],
3420 pack_list(Deps).
3421message(remove(PackDir)) -->
3422 [ 'Removing ~q and contents'-[PackDir] ].
3423message(remove_existing_pack(PackDir)) -->
3424 [ 'Remove old installation in ~q'-[PackDir] ].
3425message(download_plan(Plan)) -->
3426 [ ansi(bold, 'Installation plan:', []), nl ],
3427 install_plan(Plan, Actions),
3428 install_label(Actions).
3429message(build_plan(Plan)) -->
3430 [ ansi(bold, 'The following packs have post install scripts:', []), nl ],
3431 msg_build_plan(Plan),
3432 [ nl, ansi(bold, 'Run scripts?', []) ].
3433message(no_meta_data(BaseDir)) -->
3434 [ 'Cannot find pack.pl inside directory ~q. Not a package?'-[BaseDir] ].
3435message(search_no_matches(Name)) -->
3436 [ 'Search for "~w", returned no matching packages'-[Name] ].
3437message(rebuild(Pack)) -->
3438 [ 'Checking pack "~w" for rebuild ...'-[Pack] ].
3439message(up_to_date([Pack])) -->
3440 !,
3441 [ 'Pack ' ], msg_pack(Pack), [' is up-to-date' ].
3442message(up_to_date(Packs)) -->
3443 [ 'Packs ' ], sequence(msg_pack, [', '], Packs), [' are up-to-date' ].
3444message(installed_can_upgrade(List)) -->
3445 sequence(msg_can_upgrade_target, [nl], List).
3446message(new_dependencies(Deps)) -->
3447 [ 'Found new dependencies after downloading (~p).'-[Deps], nl ].
3448message(query_versions(URL)) -->
3449 [ 'Querying "~w" to find new versions ...'-[URL] ].
3450message(no_matching_urls(URL)) -->
3451 [ 'Could not find any matching URL: ~q'-[URL] ].
3452message(found_versions([Latest-_URL|More])) -->
3453 { length(More, Len) },
3454 [ ' Latest version: ~w (~D older)'-[Latest, Len] ].
3455message(build(Pack, PackDir)) -->
3456 [ ansi(bold, 'Building pack ~w in directory ~w', [Pack, PackDir]) ].
3457message(contacting_server(Server)) -->
3458 [ 'Contacting server at ~w ...'-[Server], flush ].
3459message(server_reply(true(_))) -->
3460 [ at_same_line, ' ok'-[] ].
3461message(server_reply(false)) -->
3462 [ at_same_line, ' done'-[] ].
3463message(server_reply(exception(E))) -->
3464 [ 'Server reported the following error:'-[], nl ],
3465 '$messages':translate_message(E).
3466message(cannot_create_dir(Alias)) -->
3467 { findall(PackDir,
3468 absolute_file_name(Alias, PackDir, [solutions(all)]),
3469 PackDirs0),
3470 sort(PackDirs0, PackDirs)
3471 },
3472 [ 'Cannot find a place to create a package directory.'-[],
3473 'Considered:'-[]
3474 ],
3475 candidate_dirs(PackDirs).
3476message(conflict(version, [PackV, FileV])) -->
3477 ['Version mismatch: pack.pl: '-[]], msg_version(PackV),
3478 [', file claims version '-[]], msg_version(FileV).
3479message(conflict(name, [PackInfo, FileInfo])) -->
3480 ['Pack ~w mismatch: pack.pl: ~p'-[PackInfo]],
3481 [', file claims ~w: ~p'-[FileInfo]].
3482message(no_prolog_response(ContentType, String)) -->
3483 [ 'Expected Prolog response. Got content of type ~p'-[ContentType], nl,
3484 '~s'-[String]
3485 ].
3486message(download(begin, Pack, _URL, _DownloadFile)) -->
3487 [ 'Downloading ' ], msg_pack(Pack), [ ' ... ', flush ].
3488message(download(end, _, _, File)) -->
3489 { size_file(File, Bytes) },
3490 [ at_same_line, '~D bytes'-[Bytes] ].
3491message(no_git(URL)) -->
3492 [ 'Cannot install from git repository ', url(URL), '.', nl,
3493 'Cannot find git program and do not know how to download the code', nl,
3494 'from this git service. Please install git and retry.'
3495 ].
3496message(git_no_https(GitURL)) -->
3497 [ 'Do not know how to get an HTTP(s) URL for ', url(GitURL) ].
3498message(git_branch_not_default(Dir, Default, Current)) -->
3499 [ 'GIT current branch on ', url(Dir), ' is not default.', nl,
3500 ' Current branch: ', ansi(code, '~w', [Current]),
3501 ' default: ', ansi(code, '~w', [Default])
3502 ].
3503message(git_not_clean(Dir)) -->
3504 [ 'GIT working directory is dirty: ', url(Dir), nl,
3505 'Your repository must be clean before publishing.'
3506 ].
3507message(git_push) -->
3508 [ 'Push release to GIT origin?' ].
3509message(git_tag(Tag)) -->
3510 [ 'Tag repository with release tag ', ansi(code, '~w', [Tag]) ].
3511message(git_release_tag_not_at_head(Tag)) -->
3512 [ 'Release tag ', ansi(code, '~w', [Tag]), ' is not at HEAD.', nl,
3513 'If you want to update the tag, please run ',
3514 ansi(code, 'git tag -d ~w', [Tag])
3515 ].
3516message(git_tag_out_of_sync(Tag)) -->
3517 [ 'Release tag ', ansi(code, '~w', [Tag]),
3518 ' differs from this tag at the origin'
3519 ].
3520
3521message(publish_failed(Info, Reason)) -->
3522 [ 'Pack ' ], msg_pack(Info), [ ' at version ~w'-[Info.version] ],
3523 msg_publish_failed(Reason).
3524
3525msg_publish_failed(throw(error(permission_error(register,
3526 pack(_),_URL),_))) -->
3527 [ ' is already registered with a different URL'].
3528msg_publish_failed(download) -->
3529 [' was already published?'].
3530msg_publish_failed(Status) -->
3531 [ ' failed for unknown reason (~p)'-[Status] ].
3532
3533candidate_dirs([]) --> [].
3534candidate_dirs([H|T]) --> [ nl, ' ~w'-[H] ], candidate_dirs(T).
3535 3536message(resolve_remove) -->
3537 [ nl, 'Please select an action:', nl, nl ].
3538message(create_pack_dir) -->
3539 [ nl, 'Create directory for packages', nl ].
3540message(menu(item(I, Label))) -->
3541 [ '~t(~d)~6| '-[I] ],
3542 label(Label).
3543message(menu(default_item(I, Label))) -->
3544 [ '~t(~d)~6| * '-[I] ],
3545 label(Label).
3546message(menu(select)) -->
3547 [ nl, 'Your choice? ', flush ].
3548message(confirm(Question, Default)) -->
3549 message(Question),
3550 confirm_default(Default),
3551 [ flush ].
3552message(menu(reply(Min,Max))) -->
3553 ( { Max =:= Min+1 }
3554 -> [ 'Please enter ~w or ~w'-[Min,Max] ]
3555 ; [ 'Please enter a number between ~w and ~w'-[Min,Max] ]
3556 ).
3557
3558 3559dep_issues(Issues) -->
3560 sequence(dep_issue, [nl], Issues).
3561
3562dep_issue(unsatisfied(Pack, Requires)) -->
3563 [ ' - Pack ' ], msg_pack(Pack), [' requires ~p'-[Requires]].
3564dep_issue(conflicts(Pack, Conflict)) -->
3565 [ ' - Pack ' ], msg_pack(Pack), [' conflicts with ~p'-[Conflict]].
3566
3571
3572install_label([link]) -->
3573 !,
3574 [ ansi(bold, 'Activate pack?', []) ].
3575install_label([unpack]) -->
3576 !,
3577 [ ansi(bold, 'Unpack archive?', []) ].
3578install_label(_) -->
3579 [ ansi(bold, 'Download packs?', []) ].
3580
3581install_plan([], []) -->
3582 [].
3583install_plan([H|T], [AH|AT]) -->
3584 install_step(H, AH), [nl],
3585 install_plan(T, AT).
3586
3587install_step(Info, keep) -->
3588 { Info.get(keep) == true },
3589 !,
3590 [ ' Keep ' ], msg_pack(Info), [ ' at version ~w'-[Info.version] ],
3591 msg_can_upgrade(Info).
3592install_step(Info, Action) -->
3593 { From = Info.get(upgrade),
3594 VFrom = From.version,
3595 VTo = Info.get(version),
3596 ( cmp_versions(>=, VTo, VFrom)
3597 -> Label = ansi(bold, ' Upgrade ', [])
3598 ; Label = ansi(warning, ' Downgrade ', [])
3599 )
3600 },
3601 [ Label ], msg_pack(Info),
3602 [ ' from version ~w to ~w'- [From.version, Info.get(version)] ],
3603 install_from(Info, Action).
3604install_step(Info, Action) -->
3605 { _From = Info.get(upgrade) },
3606 [ ' Upgrade ' ], msg_pack(Info),
3607 install_from(Info, Action).
3608install_step(Info, Action) -->
3609 { Dep = Info.get(dependency_for) },
3610 [ ' Install ' ], msg_pack(Info),
3611 [ ' at version ~w as dependency for '-[Info.version],
3612 ansi(code, '~w', [Dep])
3613 ],
3614 install_from(Info, Action),
3615 msg_downloads(Info).
3616install_step(Info, Action) -->
3617 { Info.get(commit) == 'HEAD' },
3618 !,
3619 [ ' Install ' ], msg_pack(Info), [ ' at current GIT HEAD'-[] ],
3620 install_from(Info, Action),
3621 msg_downloads(Info).
3622install_step(Info, link) -->
3623 { Info.get(link) == true,
3624 uri_file_name(Info.get(url), Dir)
3625 },
3626 !,
3627 [ ' Install ' ], msg_pack(Info), [ ' as symlink to ', url(Dir) ].
3628install_step(Info, Action) -->
3629 [ ' Install ' ], msg_pack(Info), [ ' at version ~w'-[Info.get(version)] ],
3630 install_from(Info, Action),
3631 msg_downloads(Info).
3632install_step(Info, Action) -->
3633 [ ' Install ' ], msg_pack(Info),
3634 install_from(Info, Action),
3635 msg_downloads(Info).
3636
3637install_from(Info, download) -->
3638 { download_url(Info.url) },
3639 !,
3640 [ ' from ', url(Info.url) ].
3641install_from(Info, unpack) -->
3642 [ ' from ', url(Info.url) ].
3643
3644msg_downloads(Info) -->
3645 { Downloads = Info.get(all_downloads),
3646 Downloads > 0
3647 },
3648 [ ansi(comment, ' (downloaded ~D times)', [Downloads]) ],
3649 !.
3650msg_downloads(_) -->
3651 [].
3652
3653msg_pack(Pack) -->
3654 { atom(Pack) },
3655 !,
3656 [ ansi(code, '~w', [Pack]) ].
3657msg_pack(Info) -->
3658 msg_pack(Info.pack).
3659
3663
3664msg_build_plan(Plan) -->
3665 sequence(build_step, [nl], Plan).
3666
3667build_step(Info) -->
3668 [ ' Build ' ], msg_pack(Info), [' in directory ', url(Info.installed) ].
3669
3670msg_can_upgrade_target(Info) -->
3671 [ ' Pack ' ], msg_pack(Info),
3672 [ ' is installed at version ~w'-[Info.version] ],
3673 msg_can_upgrade(Info).
3674
3675pack_list([]) --> [].
3676pack_list([H|T]) -->
3677 [ ' - Pack ' ], msg_pack(H), [nl],
3678 pack_list(T).
3679
3680label(remove_only(Pack)) -->
3681 [ 'Only remove package ~w (break dependencies)'-[Pack] ].
3682label(remove_deps(Pack, Deps)) -->
3683 { length(Deps, Count) },
3684 [ 'Remove package ~w and ~D dependencies'-[Pack, Count] ].
3685label(create_dir(Dir)) -->
3686 [ '~w'-[Dir] ].
3687label(install_from(git(URL))) -->
3688 !,
3689 [ 'GIT repository at ~w'-[URL] ].
3690label(install_from(URL)) -->
3691 [ '~w'-[URL] ].
3692label(cancel) -->
3693 [ 'Cancel' ].
3694
3695confirm_default(yes) -->
3696 [ ' Y/n? ' ].
3697confirm_default(no) -->
3698 [ ' y/N? ' ].
3699confirm_default(none) -->
3700 [ ' y/n? ' ].
3701
3702msg_version(Version) -->
3703 [ '~w'-[Version] ].
3704
3705msg_can_upgrade(Info) -->
3706 { Latest = Info.get(latest_version) },
3707 [ ansi(warning, ' (can be upgraded to ~w)', [Latest]) ].
3708msg_can_upgrade(_) -->
3709 [].
3710
3711
3712 3715
3716local_uri_file_name(URL, FileName) :-
3717 uri_file_name(URL, FileName),
3718 !.
3719local_uri_file_name(URL, FileName) :-
3720 uri_components(URL, Components),
3721 uri_data(scheme, Components, File), File == file,
3722 uri_data(authority, Components, FileNameEnc),
3723 uri_data(path, Components, ''),
3724 uri_encoded(path, FileName, FileNameEnc).
3725
3726det_if(Cond, Goal) :-
3727 ( Cond
3728 -> Goal,
3729 !
3730 ; Goal
3731 ).
3732
3733member_nonvar(_, Var) :-
3734 var(Var),
3735 !,
3736 fail.
3737member_nonvar(E, [E|_]).
3738member_nonvar(E, [_|T]) :-
3739 member_nonvar(E, T)