29
30:- module(pack_analyzer,
31 [ pack_analysis/2, 32 xref_pack/1, 33 pack_prolog_entry/1, 34 xref_pack_file/2, 35 pack_members/2, 36 pack_open_entry/3 37 ]). 38:- use_module(library(lists)). 39:- use_module(library(apply)). 40:- use_module(library(archive)). 41:- use_module(library(filesex)). 42:- use_module(library(prolog_xref)). 43:- use_module(library(prolog_source)). 44:- use_module(library(option)). 45:- use_module(library(debug)). 46:- use_module(library(git)). 47
48:- use_module(pack_mirror). 49:- use_module(pack_info). 50
55
56:- dynamic
57 pack_dependency/4. 58
59:- meta_predicate
60 find_unique(?, 0, ?, ?). 61
81
82pack_analysis(Pack,
83 [ size(Size),
84 files(Members)
85 | Results
86 ]) :-
87 pack_members(Pack, Members),
88 maplist(arg(2), Members, SizeByFile),
89 sum_list(SizeByFile, Size),
90 xref_pack(Pack),
91 find_unique(Readme, readme(Pack, Members, Readme), Results, Res0),
92 find_unique(Info, file_info(Pack, Members, Info), Res0, Res1),
93 find_unique(ReqF, required_file(Pack, ReqF), Res1, Res2),
94 find_unique(ReqP, required_predicate(Pack, ReqP), Res2, []).
95
96find_unique(Templ, Goal, List, Tail) :-
97 findall(Templ, Goal, List0),
98 sort(List0, List1),
99 append(List1, Tail, List).
100
101readme(Pack, Members, readme(Text)) :-
102 member(file(Name, _Size), Members),
103 downcase_atom(Name, Down),
104 readme_file(Down), !,
105 setup_call_cleanup(
106 pack_open_entry(Pack, Name, Stream),
107 read_stream_to_codes(Stream, Codes),
108 close(Stream)),
109 string_to_list(Text, Codes).
110
111readme_file(readme).
112readme_file('readme.txt').
113
114
115file_info(Pack, Members,
116 prolog_source(Entry, Size, Module, Exports, Documentation)) :-
117 xref_pack_source(Pack, Entry, Source),
118 memberchk(file(Entry,Size), Members),
119 ( xref_module(Source, Module)
120 -> findall(export(Name/Arity, Info),
121 ( xref_exported(Source, Head),
122 functor(Head, Name, Arity),
123 ( xref_comment(Source, Head, Summary, PredComment)
124 -> Info = [doc(Summary, PredComment)]
125 ; Info = []
126 )
127 ),
128 Exports)
129 ; Module = (-), 130 Exports = []
131 ),
132 ( xref_comment(Source, Title, FileComment)
133 -> Documentation = [ doc(Title, FileComment) ]
134 ; Documentation = []
135 ).
136
137required_file(Pack, required_file(Spec, From)) :-
138 xref_pack_source(Pack, _, Source),
139 xref_uses_file(Source, Spec, File),
140 classify_file(Pack, File, From0),
141 from_class(From0, From).
142
143from_class(pack(Pack), pack(Pack)) :- !.
144from_class(Alias, From) :-
145 functor(Alias, From, 1), !.
146from_class(From, From).
147
148classify_file(Pack, File, From) :-
149 ( xref_pack_source(Pack, _, File)
150 -> From = pack
151 ; absolute_file_name(pack(.), PackRoot,
152 [ file_type(directory),
153 solutions(all)
154 ]),
155 atom_concat(PackRoot, Local, File)
156 -> atomic_list_concat(Segments, /, Local),
157 Segments = [FromPack|_],
158 From = pack(FromPack)
159 ; absolute_file_name(swi(.), SwiRoot,
160 [ file_type(directory)
161 ]),
162 atom_concat(SwiRoot, _, File)
163 -> file_name_on_path(File, From)
164 ; From = File 165 ).
166
170
171required_predicate(Pack, required_predicate(Name/Arity, From)) :-
172 xref_pack_source(Pack, _, Source),
173 xref_called(Source, Head, _By),
174 functor(Head, Name, Arity),
175 ( xref_defined(Source, Head, How)
176 -> classify_predicate_source(How, Pack, From)
177 ; predicate_property(Head, iso)
178 -> From = iso
179 ; predicate_property(system:Head, visible)
180 -> From = swi
181 ; predicate_property(Head, autoload(Autoload))
182 -> classify_file(Pack, Autoload, From)
183 ; From = undefined
184 ).
185
186classify_predicate_source(imported(File), Pack, From) :-
187 classify_file(Pack, File, From).
188
189
193
194xref_pack(Pack) :-
195 absolute_file_name(Pack, PackPath),
196 retractall(pack_dependency(PackPath, _Spec, _How, _Dep)),
197 pack_members(PackPath, Members),
198 maplist(arg(1), Members, Entries),
199 include(pack_prolog_entry, Entries, PrologEntries),
200 maplist(xref_pack_file(PackPath), PrologEntries).
201
202pack_prolog_entry(Entry) :-
203 sub_atom(Entry, 0, _, _, 'prolog/'),
204 file_name_extension(_, Ext, Entry),
205 user:prolog_file_type(Ext, prolog), !.
206
207
211
212xref_pack_file(Pack, File) :-
213 catch(xref_pack_file_2(Pack, File),
214 E, print_message(error, E)), !.
215xref_pack_file(Pack, File) :-
216 print_message(warning,
217 error(goal_failed(xref_pack_file(Pack, File)),
218 _)).
219
220xref_pack_file_2(Pack, File) :-
221 exists_directory(Pack), !,
222 directory_file_path(Pack, File, Path),
223 xref_source(Path, [register_called(all)]).
224xref_pack_file_2(Pack, File) :-
225 absolute_file_name(Pack, AbsPack,
226 [ access(read)
227 ]),
228 directory_file_path(AbsPack, File, Path),
229 xref_source(Path, [register_called(all)]).
230
231
232 235
240
241pack_open_entry(Directory, Entry, Stream) :-
242 exists_directory(Directory), !,
243 directory_file_path(Directory, Entry, File),
244 open(File, read, Stream).
245pack_open_entry(Archive, Entry, Stream) :-
246 ar_prefix(Archive, Prefix),
247 atom_concat(Prefix, Entry, Name),
248 setup_call_cleanup(
249 archive_open(Archive, Handle, []),
250 ( archive_next_header(Handle, Name),
251 archive_open_entry(Handle, Stream)
252 ),
253 archive_close(Handle)),
254 format(atom(StreamName), '~w/~w', [Archive, Entry]),
255 set_stream(Stream, file_name(StreamName)).
256
257:- dynamic
258 ar_prefix_cache/2,
259 ar_members_cache/3. 260
261ar_prefix(Archive, Prefix) :-
262 ar_prefix_cache(Archive, Prefix0), !,
263 Prefix = Prefix0.
264ar_prefix(Archive, Prefix) :-
265 ar_pack_members(Archive, _, Prefix),
266 assertz(ar_prefix_cache(Archive, Prefix)).
267
273
274:- dynamic
275 pack_member_cache/3. 276
277pack_members(Dir, Members) :-
278 time_file(Dir, T),
279 pack_member_cache(Dir, T, Members0), !,
280 Members = Members0.
281pack_members(Dir, Members) :-
282 pack_members_no_cache(Dir, Members0),
283 time_file(Dir, T),
284 asserta(pack_member_cache(Dir, T, Members0)),
285 Members = Members0.
286
287pack_members_no_cache(Directory, Members) :-
288 is_git_directory(Directory), !,
289 git_ls_tree(Entries, [directory(Directory)]),
290 include(git_blob, Entries, Blobs),
291 maplist(git_entry, Blobs, Members).
292pack_members_no_cache(Directory, Members) :-
293 exists_directory(Directory), !,
294 recursive_directory_files(Directory, Files),
295 maplist(file_entry(Directory), Files, Members).
296pack_members_no_cache(Archive, Members) :-
297 E = error(archive_error(_,_),_),
298 catch(ar_pack_members(Archive, Members0, Prefix),
299 E, bad_archive(Archive, E)),
300 maplist(strip_prefix(Prefix), Members0, Members).
301
302bad_archive(Archive, Error) :-
303 delete_file(Archive),
304 throw(Error).
305
306git_blob(object(_Mode, blob, _Hash, _Size, _Name)).
307git_entry(object(_Mode, blob, _Hash, Size, Name), file(Name, Size)).
308
309ar_pack_members(Archive, Members, Prefix) :-
310 ( ar_members_cache(Archive, Members0, Prefix0)
311 -> true
312 ; read_ar_pack_members(Archive, Members0, Prefix0)
313 -> asserta(ar_members_cache(Archive, Members0, Prefix0))
314 ),
315 Members = Members0,
316 Prefix = Prefix0.
317
318read_ar_pack_members(Archive, Members0, Prefix) :-
319 setup_call_cleanup(
320 archive_open(Archive, Handle, []),
321 findall(Member, ar_member(Handle, Member), Members0),
322 archive_close(Handle)),
323 ( member(file(InfoFile,_), Members0),
324 atom_concat(Prefix, 'pack.pl', InfoFile)
325 -> true
326 ; existence_error(pack_file, 'pack.pl')
327 ).
328
329ar_member(Handle, Entry) :-
330 repeat,
331 ( archive_next_header(Handle, File)
332 -> true
333 ; !, fail
334 ),
335 archive_header_property(Handle, filetype(Type)),
336 make_entry(Type, Handle, File, Entry).
337
338make_entry(file, Handle, File, file(File, Size)) :- !,
339 archive_header_property(Handle, size(Size)).
340make_entry(link, Handle, File, link(File, Target)) :- !,
341 archive_header_property(Handle, link_target(Target)).
342make_entry(directory, _, _, _) :- !,
343 fail.
344make_entry(Type, _, Name, Entry) :-
345 atom(Type), !,
346 Entry =.. [Type, Name].
347make_entry(Type, _, Name, _Entry) :-
348 print_message(warning, unknown_archive_type(Type, Name)),
349 fail.
350
351strip_prefix(Prefix, Term0, Term) :-
352 Term0 =.. [Type, Name, Size],
353 atom_concat(Prefix, Stripped, Name),
354 Term =.. [Type, Stripped, Size].
355
356file_entry(Pack, File, file(File,Size)) :-
357 directory_file_path(Pack, File, Path),
358 size_file(Path, Size).
359
363
364recursive_directory_files(Dir, Files) :-
365 dir_prefix(Dir, Prefix),
366 recursive_directory_files(Dir, Prefix, Files, []).
367
368recursive_directory_files(Dir, Prefix, AllFiles, Rest) :-
369 directory_files(Dir, Files),
370 dir_files(Files, Dir, Prefix, AllFiles, Rest).
371
372dir_files([], _, _, Files, Files).
373dir_files([H|T], Dir, Prefix, Files, Rest) :-
374 ( special(H)
375 -> dir_files(T, Dir, Prefix, Files, Rest)
376 ; directory_file_path(Dir, H, Entry),
377 ( exists_directory(Entry)
378 -> recursive_directory_files(Entry, Prefix, Files, Rest0)
379 ; atom_concat(Prefix, File, Entry),
380 Files = [File|Rest0]
381 ),
382 dir_files(T, Dir, Prefix, Rest0, Rest)
383 ).
384
385dir_prefix(., '') :- !.
386dir_prefix(Dir, Prefix) :-
387 ( sub_atom(Dir, _, _, 0, /)
388 -> Prefix = Dir
389 ; atom_concat(Dir, /, Prefix)
390 ).
391
392special(.).
393special(..).
394
395 398
399:- multifile
400 prolog:xref_open_source/2,
401 prolog:xref_source_identifier/2,
402 prolog:xref_source_file/3. 403
409
410prolog:xref_open_source(File, Stream) :-
411 pack_prefix(Pack, Prefix),
412 atom_concat(Prefix, Entry, File),
413 pack_open_entry(Pack, Entry, Stream).
414
416
417prolog:xref_source_identifier(Path, Path) :-
418 atom(Path),
419 pack_mirror_directory(MirrorDir),
420 sub_atom(Path, 0, _, _, MirrorDir),
421 atom(Path),
422 pack_prefix(_Pack, Prefix),
423 sub_atom(Path, 0, _, _, Prefix), !.
424
428
429pack_file(Path, Pack, Entry) :-
430 pack_prefix(Pack, Prefix),
431 atom_concat(Prefix, Entry, Path),
432 pack_members(Pack, Members),
433 memberchk(file(Entry,_Size), Members).
434
439
440resolve_pack_file(library(File), Source, SourcePack, SourceEntry) :-
441 ( atom(File)
442 -> FileName = File
443 ; path_segments_atom(File, FileName)
444 ),
445 directory_file_path(prolog, FileName, EntryNoExt),
446 user:prolog_file_type(Ext, prolog),
447 file_name_extension(EntryNoExt, Ext, SourceEntry),
448 pack_file(Source, SourcePack, SourceEntry).
449
451
452assert_dependency(OrgPack, OrgSpec, How, Src) :-
453 pack_dependency(OrgPack, OrgSpec, How, Src), !.
454assert_dependency(OrgPack, OrgSpec, How, Src) :-
455 asserta(pack_dependency(OrgPack, OrgSpec, How, Src)).
456
458
459prolog:xref_source_file(library(File), Source, Options) :-
460 option(relative_to(Origin), Options),
461 pack_file(Origin, OrgPack, _OrigEntry),
462 debug(pack(xref), 'Search for ~q from pack ~q',
463 [library(File), OrgPack]),
464 findall(t(Src, SrcPack, SrcEntry),
465 resolve_pack_file(library(File), Src, SrcPack, SrcEntry),
466 Triples),
467 ( select(t(Source, OrgPack, _), Triples, Alt)
468 -> true
469 ; select(t(Source, _, _), Triples, Alt),
470 assert_dependency(OrgPack, library(File), dep, Source)
471 ),
472 forall(member(t(AltSrc,_,_), Alt),
473 assert_dependency(OrgPack, library(File), alt, AltSrc)).
474prolog:xref_source_file(Spec, Source, _Options) :-
475 atom(Spec),
476 ( pack_file(Spec, _, _)
477 -> Source = Spec
478 ; user:prolog_file_type(Ext, prolog),
479 file_name_extension(Spec, Ext, Source),
480 pack_file(Source, _, _)
481 ),
482 debug(pack(xref), 'Resolved ~q to ~q', [Spec, Source]).
483
487
488xref_pack_source(Pack, Entry, Source) :-
489 xref_current_source(Source),
490 pack_prefix(Pack, Prefix),
491 atom_concat(Prefix, Entry, Source).
492
493
494pack_prefix(Archive, Prefix) :-
495 pack_archive(_Pack, _Hash, Archive),
496 atom_concat(Archive, /, Prefix)