/* Part of ClioPatria SeRQL and SPARQL server Author: Jan Wielemaker E-mail: J.Wielemaker@cs.vu.nl WWW: http://www.swi-prolog.org Copyright (C): 2010, University of Amsterdam, VU University Amsterdam This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA As a special exception, if you link this library with other files, compiled with a Free Software compiler, to produce an executable, this library does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ :- module(cpack_dependency, [ file_used_by_file_in_package/3, % +File, -UsedBy, -Package cpack_requires/3, % +Package, -Package, -Why cpack_conflicts/3, % +Package, -Package, -Why cpack_list/2, % +Package, -ListOfImplied cpack_list/3, % +Package, -ListOfImplied, -Dependencies cpack_not_satisfied/2, % +Package, -Reasons file_not_satisfied/2, % +File, -Reasons file_imports_from/3, % +File, -Imports, -From file_calls_public_from/3 % +File, -UsedFile, +PI ]). :- use_module(library(assoc)). :- use_module(library(ugraphs)). :- use_module(library(semweb/rdf_db)). :- use_module(library(semweb/rdfs)). :- use_module(repository). /** Query the CPACK dependency graph This module queries the RDF graph produced by xref.pl to compute high-level dependencies between objects. Currently, we keep track of: * Requirement reasons - Files needed by one and provided by another package - Tokens needed by one and provided by another package * Conflict reasons - Packages holding files that provide the same module - Packages holding files with the same path * Satisfied status @tbd Extend reasoning */ %% file_used_by_file_in_package(+File, -UsedBy, -Package) is nondet. % % True when UsedBy is a file in Package that imports File. file_used_by_file_in_package(File, UsedBy, Pack) :- rdf_has(File, cpack:resolves, FileRef), rdf_has(UsedBy, cpack:usesFile, FileRef), rdf_has(UsedBy, cpack:inPack, Pack). %% cpack_requires(+Package, -Required, -Reasons) is nondet. % % True when Package requires Required. Reasons is an ordered set % of reasons. Individual reasons are one of: % % * token(Name) % Package requires _token_ that is provided by Required. % * file_ref(FileRef) % Package uses the file FileRef, which is provided by Required cpack_requires(Package, Required, AllReasons) :- setof(Why, cpack_requires_by(Package, Required, Why), AllReasons). cpack_requires_by(Package, Required, token(Token)) :- rdf_has(Package, cpack:requires, Req), ( rdf_is_literal(Req) -> Token = Req ; rdf_has(Req, cpack:name, Token) ), rdf_has(Required, cpack:provides, Token). cpack_requires_by(Package, Required, file_ref(FileRef)) :- rdf_has(File, cpack:inPack, Package), rdf_has(File, cpack:usesFile, FileRef), rdf_has(ReqFile, cpack:resolves, FileRef), rdf_has(ReqFile, cpack:inPack, Required), Required \== Package. %% cpack_conflicts(+Package, -Conflict, -Why) is nondet. % % True when Package and Conflict are in conflict. Defined % conflicts are: % % * same_module(Module,File1,File2) % Both files define the same module. They cannot be loaded into % the same Prolog instance. Note that this can cause a package % to conflict with itself! % * same_file(Path,File1,File2) % Two packages define files at the same path. This is actually % not an issue in itself. It only becomes an issue if there are % file_ref objects that resolve them ambiguously. cpack_conflicts(Package, Conflict, AllReasons) :- setof(Why, cpack_conflicts_by(Package, Conflict, Why), AllReasons). cpack_conflicts_by(Package, Conflict, same_module(M,File1,File2)) :- rdf_has(File1, cpack:module, Module), rdf_has(File2, cpack:module, Module), File1 \== File2, Module = literal(M), rdf_has(Package, cpack:in_file, File1), rdf_has(Conflict, cpack:in_file, File2). cpack_conflicts_by(Package, Conflict, same_file(Path,File1,File2)) :- rdf_has(File1, cpack:path, LPath), rdf_has(File2, cpack:path, LPath), File1 \== File2, LPath = literal(Path), rdf_has(Package, cpack:in_file, File1), rdf_has(Conflict, cpack:in_file, File2). /******************************* * GRAPH * *******************************/ %% cpack_list(+Pack, -PackList) is det. %% cpack_list(+Pack, -PackList, -UGraph) is det. % % PackList is a list of all packages that need to be installed to % get Pack working. This list is ensured to contain Pack. % % @param Pack is either the URI of a single Pack or a list of % these. % @param UGraph is a ugraph of Pack-ListOfRequired. See % library(ugraph) for details. % % @tbd Toplogical sorting may not be possible. As ordering is % not always necessary, we should try to relax % dependencies if a topological sort is not possible due % to cycles. There are two heuristics here. First of % all, explicit (token) dependencies may be removed and % second, libraries must be loaded before applications. cpack_list(Pack, Packs) :- cpack_list(Pack, Packs, _). cpack_list(Pack, Packs, Ugraph) :- dependency_ugraph(Pack, Ugraph), ( sort_dependencies(Ugraph, Packs) -> check_conflicts(Packs), check_satisfied(Packs) ; domain_error(non_cyclic_dependency_structure, Ugraph) ). check_conflicts(Packs) :- append(_,[P1|Rest], Packs), cpack_conflicts(P1, Conflict, Reasons), member(Conflict, Rest), !, throw(error(cpack_error(conflict(P1, Conflict, Reasons)), _)). check_conflicts(_). check_satisfied(Packs) :- maplist(cpack_satisfied, Packs). cpack_satisfied(Pack) :- cpack_not_satisfied(Pack, Reasons), !, throw(error(cpack_error(not_satisfied(Pack, Reasons)), _)). cpack_satisfied(_). %% dependency_ugraph(+Pack, -Ugraph) is det. % % Create a full dependency graph for pack as a ugraph. dependency_ugraph(Pack, UGraph) :- ( is_list(Pack) -> Agenda = Pack ; Agenda = [Pack] ), empty_assoc(Visited), dependency_ugraph(Agenda, Visited, Graph), keysort(Graph, UGraph). dependency_ugraph([], _, []). dependency_ugraph([H|T], Visited, Graph) :- ( get_assoc(H, Visited, _) -> dependency_ugraph(T, Visited, Graph) ; findall(Required, cpack_requires(H, Required, _), RList0), sort(RList0, RList), Graph = [H-RList|More], put_assoc(H, Visited, true, Visited2), append(RList, T, Agenda), dependency_ugraph(Agenda, Visited2, More) ). %% sort_dependencies(+Graph, -List) % % List is a package list that satisfies the dependencies in Graph. sort_dependencies(Graph, List) :- top_sort(Graph, RList), !, reverse(RList, List). sort_dependencies(Graph, List) :- connect_graph(Graph, Start, Connected), top_sort(Connected, [Start|RList]), !, reverse(RList, List). %% connect_graph(+Graph, -Start, -Connected) is det. % % Turn Graph into a connected graph by putting a shared starting % point before all vertices. connect_graph([], 0, []) :- !. connect_graph(Graph, Start, [Start-Vertices|Graph]) :- vertices(Graph, Vertices), Vertices = [First|_], before(First, Start). %% before(+Term, -Before) is det. % % Unify Before to a term that comes before Term in the standard % order of terms. % % @error instantiation_error if Term is unbound. before(X, _) :- var(X), !, instantiation_error(X). before(Number, Start) :- number(Number), !, Start is Number - 1. before(_, 0). /******************************* * SATISFIED * *******************************/ %% cpack_not_satisfied(+Package, -WhyNot) is semidet. % % True when WhyNot describes why Package is not satisfied. cpack_not_satisfied(Pack, AllReasons) :- setof(Due, cpack_not_satisfied_due(Pack, Due), AllReasons). cpack_not_satisfied_due(Package, no_token(Token)) :- rdf_has(Package, cpack:requires, Req), ( rdf_is_literal(Req) -> Token = Req ; rdf_has(Req, cpack:name, Token) ), \+ rdf_has(_, cpack:provides, Token). cpack_not_satisfied_due(Package, file(File, Problems)) :- rdf_has(File, cpack:inPack, Package), file_not_satisfied(File, Problems). %% file_not_satisfied(+File, -Reasons) is semidet. % % True when File's conditions are not satisfied due to Reasons. file_not_satisfied(File, AllReasons) :- setof(Due, file_not_satisfied_due(File, Due), AllReasons). %% file_not_satisfied_due(+File, -Problem) % % True when Conflict describes an import problem for File. There % are two types of import problems: % % * double_import(PI, File1, File2) % File imports File1 and File2 using use_module/1, both of which % export PI. % * locally_defined(PI, File) % A locally defined predicate is also imported from File. % * file_not_found(FileRef) % The given symbolic path cannot be found. % % @tbd We do not yet keep track of locally defined predicates file_not_satisfied_due(File, double_import(PI,File1,File2)) :- file_imports_pi_from(File, File1, PI), file_imports_pi_from(File, File2, PI), File1 \== File2. file_not_satisfied_due(File, file_not_found(FileRef)) :- rdf_has(File, cpack:usesFile, FileRef), rdfs_individual_of(FileRef, cpack:'FileRef'), \+ rdf_has(_, cpack:resolves, FileRef). file_not_satisfied_due(File, predicate_not_found(PI)) :- LPI = literal(PI), rdf_has(File, cpack:requiresPredicate, LPI), \+ file_imports_pi_from(File, _, PI), \+ file_calls_public_from(File, _, PI), \+ other_source(PI). other_source(API) :- atom_to_term(API, PI, []), pi_head(PI, Head), ( predicate_property(Head, multifile) ; predicate_property(Head, autoload(_)) ). pi_head(M:PI, M:Head) :- !, pi_head(PI, Head). pi_head(Name/Arity, Head) :- functor(Head, Name, Arity). %% file_imports_from(+File, -Predicates, -From) is nondet. % % True if File imports Predicates from the file From. % % @param Predicates is a list of canonical predicate indicators. file_imports_from(File, PIs, From) :- setof(PI, file_imports_pi_from(File, From, PI), PIs). file_imports_pi_from(File, UsedFile, PI) :- rdf_has(File, cpack:usesFile, Uses), ( rdfs_individual_of(Uses, cpack:'FileRef') -> rdf_has(UsedFile, cpack:resolves, Uses) ; UsedFile = Uses ), rdf_has(UsedFile, cpack:exportsPredicate, literal(PI)). %% file_calls_public_from(+File, -UsedFile, +PI) is semidet. % % True if PI is a module-qualified term that can be called in % UsedFile, that is imported from File. file_calls_public_from(File, UsedFile, PI) :- ( rdf_has(UsedFile, cpack:publicPredicate, literal(PI)) -> true ; atom_to_term(PI, M:PPI, []), rdf_has(UsedFile, cpack:module, literal(M)), format(atom(Plain), '~q', [PPI]), ( rdf_has(UsedFile, cpack:exportsPredicate, literal(Plain)) -> true ; rdf_has(UsedFile, cpack:publicPredicate, literal(Plain)) ) ), ( rdf_has(File, cpack:usesFile, UsedFile) -> true ; rdf_has(UsedFile, cpack:resolves, Uses), rdf_has(File, cpack:usesFile, Uses) ), !.