<?php
/*
Copyright (C) 2001-2004 ZZOSS GbR, http://www.zzoss.com
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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
*/
/**
@version $Id: Dependency.php,v 1.6 2004/04/02 12:34:34 ordnas Exp $
@copyright Copyright © 2001-2004 ZZ/OSS GbR, http://www.zzoss.com
@license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
*/
require_once 'Package.php';
class ZZOSS_PackageDependency
{
var $queue;
var $step;
var $process;
var $result;
var $deps = array();
var $packages_check = array();
var $package_name;
var $package;
var $dep;
var $error = false;
var $pending = array();
/**
* Stack of those packages that depend on another package.
*/
var $_circular_pkgs = array();
/**
* Stack of those packages that are needeb by another package.
*/
var $_circular_deps = array();
/**
* Packages not defined in application.xml.
*/
var $undefined = array();
function run($packages, $packages_resolve, $packages_versions, $actions)
{
$this->packages = $packages;
$this->packages_resolve = $packages_resolve;
$this->packages_versions = $packages_versions;
$this->actions = $actions;
// currently only install dependencies are checked
// in future an enhanced algorithm will check for proper dependencies
$this->_composePackageArray('I');
// catch errors
$this->_checkDependencies();
// HACK: todo: extend dependency function for uninstall / update deps
$this->step--;
if($process_queue_array = $this->_composeQueue('R')){
$this->queue[$this->step] = $process_queue_array;
$this->step++;
}
if($process_queue_array = $this->_composeQueue('U')){
$this->queue[$this->step] = $process_queue_array;
$this->step++;
}
return $this->queue;
}
function hasPendingDeps()
{
return count($this->pending);
}
function getDeps()
{
return $this->result;
}
function hasUndefinedPackages()
{
return count($this->undefined);
}
function getUndefinedPackages()
{
return $this->undefined;
}
function getPending()
{
return $this->pending;
}
function getError()
{
return $this->error;
}
function isError()
{
return strlen($this->error);
}
function getCheckedNames()
{
return $this->packages_check_names;
}
function _composeQueue($char) {
$result = false;
if(is_array($this->actions)) {
foreach($this->actions as $key => $val) {
if($val == $char) {
$name = $this->packages_resolve[$key];
$result[$name] = $char;
}
}
}
return $result;
}
function _composePackageArray($value)
{
$result = array();
if(is_array($this->actions)) {
foreach($this->actions as $key=>$val) {
if($val == $value) {
$name = $this->packages_resolve[$key];
$this->packages_check[$name] = $this->packages[$name];
$this->packages_check_names[$this->packages[$name]['name']] = true;
}
}
}
}
function _checkDependencies()
{
$result = array();
if(is_array($this->packages_check)) {
$progress = true;
$this->step = 0;
$packages_new = $this->packages;
while($progress) {
$progress = false;
$this->packages = $packages_new;
//echo $this->step.'<hr/>';
foreach($this->packages_check as $key => $val) {
$this->package_name = $key;
$this->package = $val;
if(!isset($val["_solved"]) || !$val["_solved"]) {
if($this->_checkDependency()){
// TODO: extend function for uninstall / update dependencies
//echo 'Resolved: '.$this->step.': '.$key.'<br/>';
$this->queue[$this->step][$key] = 'I';
$progress = true;
$packages_new[$key]["installed"] = $val["release"]["version"];
$this->packages_check[$key]["_solved"] = true;
} else {
/*
if(!isset($this->queue[$this->step][$key])){
$this->queue[$this->step+1][$key] = 'I';
}
*/
//echo 'Not resolved: '.$this->step.': '.$key.'<br/>';
}
}
}
$this->step++;
}
/*
echo '<pre>';
print_r($packages_new);
echo '</pre>';
*/
}
}
function _checkDependency()
{
$resolve = true;
/*
echo '<pre>';
print_r($this->package);
echo '<pre>';
*/
if(!isset($this->package["release"]["deps"])){
return true;
}
$deps = $this->package["release"]["deps"]["dep"];
if(!is_array($deps) || !count($deps)) {
return true;
} else {
//echo '<h1>'.$this->package['name'].'</h1>';
//echo '<pre>';
//print_r($deps);
//echo '</pre>';
foreach($deps as $dep) {
$this->dep = ZZOSS_InstallerUtils::trimArray($dep);
// check for package dependency
if(!$this->_checkDependencyType()){
$resolve = false;
}
}
}
//var_dump($resolve); echo '<br/>';
return $resolve;
}
/**
* @todo Rewrite to use dep plugins, just like other plugins
* @todo Routines for all PEAR deps
*/
function _checkDependencyType()
{
$dep_result = true;
//$this->deps = array();
switch($this->dep['@']['type']){
case 'pkg':
// If the dpendency is optional, don't bother about it
if(isset($this->dep['@']['optional']) && $this->dep['@']['optional'] == 'yes'){
return true;
}
// Check if the needed package is defined in application.xml
if(!isset($this->packages_versions[$this->dep["#"]])){
//echo $this->dep["#"].'<- '.$this->package_name.'<br>';
$pkg_undef_name = $this->dep["#"];
if(isset($this->dep["@"]["version"]) && strlen($this->dep["@"]["version"])){
$pkg_undef_name .= '-'.$this->dep["@"]["version"];
$this->undefined[$pkg_undef_name]['version'] = $this->dep["@"]["version"];
}
$this->undefined[$pkg_undef_name]['name'] = $this->dep["#"];
$this->undefined[$pkg_undef_name]['needed_by'][] = array($this->package_name => $this->dep["@"]["rel"]);
//print_r($this->undefined[$pkg_undef_name]['needed_by']);
//$this->undefined[$pkg_undef_name]['needed_by'] = array_unique($this->undefined[$pkg_undef_name]['needed_by']);
//print_r($this->undefined[$pkg_undef_name]['needed_by']);
$this->error = 'Some packages not defined by application';
} else {
$versions = $this->packages_versions[$this->dep["#"]];
$dep_result = false;
$pending = array();
foreach($versions as $v_available => $val){
if(!isset($this->dep["@"]["version"])){
$this->dep["@"]["version"] = null;
}
if(!$this->_isResolved($this->dep['@']['rel'], $this->dep["#"], $v_available, $this->dep["@"]["version"])){
$pending[$this->dep["#"].'-'.$v_available]['needed_by'][] = array($this->package_name => $this->dep["@"]["rel"]);
//$pending[$this->dep["#"].'-'.$v_available]['needed_by'] = array_unique($pending[$this->dep["#"].'-'.$v_available]['needed_by']);
} else {
$dep_result = true;
unset($this->pending[$this->dep['#'].'-'.$v_available]);
//echo 'UNSET PENDING: '.$this->dep['#'].'<br/>';
}
}
if(!$dep_result){
//echo 'SET PENDING: '.$this->dep['#'].'<br/>';
$this->pending = $pending + $this->pending;
}
}
break;
}
return $dep_result;
}
function _isResolved($dep_rel, $dep_name, $v_available, /*$is_installed,*/ $v_required = NULL)
{
$is_available = false;
// If no version has been defined, we check, if the available version
// is in the list of needed packages
if(isset($this->packages_check_names[$dep_name])){
$is_available = true;
}
$is_installed = isset($this->packages[$this->dep["#"].'-'.$v_available]["installed"]);
$is_resolved = true;
switch($dep_rel) {
case 'ge':
// greater-or-equal comparing
if($is_available && $is_installed){
//echo "ge - $dep_name: $v_available >= $v_required<br/>";
$is_resolved = version_compare($v_available, $v_required, $dep_rel);
} else {
//var_dump($is_installed);
//echo "ge - $dep_name: !\$is_available<br/>";
$is_resolved = false;
}
break;
case 'le':
// lower-or-equal comparing
// greater-or-equal comparing
if($is_available && $is_installed){
$is_resolved = version_compare($v_available, $v_required, $dep_rel);
} else {
$is_resolved = false;
}
break;
case 'has':
//echo "has - $dep_name: $is_installed<br/>";
$is_resolved = $is_installed;
//$is_resolved = $is_available;
break;
default:
$dep_rel = 'eq';
// equal comparing
// greater-or-equal comparing
if($is_available && $is_installed){
$is_resolved = version_compare($v_available, $v_required, $dep_rel);
} else {
$is_resolved = false;
}
break;
}
// check for circular dependencies:
// if the needed package has a dependency on a package that depends on it
if(
isset($this->_circular_pkgs[$this->dep["#"]]) &&
in_array($this->package['name'], $this->_circular_pkgs[$this->dep["#"]]) &&
isset($this->_circular_deps[$this->package['name']]) &&
in_array($this->dep["#"], $this->_circular_deps[$this->package['name']])){
/*
echo "Circular dependency between '".$this->dep["#"]."' and '".$this->package['name']."'.";
//$this->_unsetCircular();
echo '<pre>deps:';
print_r($this->_circular_deps);
echo 'pkgs:';
print_r($this->_circular_pkgs);
echo '</pre>';
*/
return PEAR::raiseError("Circular dependency between '".$this->dep["#"]."' and '".$this->package['name']."'.");
}
if(!$is_resolved) {
$this->_setCircularDeps();
} else {
$this->_unsetCircular();
}
return $is_resolved;
}
function _setCircularDeps()
{
if(!isset($this->_circular_deps[$this->dep["#"]]) && !isset($this->_circular_pkgs[$this->package['name']])) {
/*
echo '<br/>';
echo "Setting deps: '".$this->dep["#"]."'.";
echo '<br/>';
echo "Setting pkgs: '".$this->package['name']."'.";
echo '<hr/>';
*/
$this->_circular_deps[$this->dep["#"]][] = $this->package['name'];
$this->_circular_pkgs[$this->package['name']][] = $this->dep["#"];
}
}
function _unsetCircular()
{
/*
echo '<br/>';
echo "Unsetting deps: '".$this->dep["#"]."'.";
echo '<br/>';
echo "Unsetting pkgs: '".$this->package['name']."'.";
echo '<hr/>';
*/
unset($this->_circular_deps[$this->dep["#"]]);
unset($this->_circular_pkgs[$this->package['name']]);
}
}
?>