89 lines
3.0 KiB
Python
89 lines
3.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2016 Mirantis, Inc.
|
|
#
|
|
# 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 program; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
import logging
|
|
import six
|
|
|
|
from collections import OrderedDict
|
|
from packetary.objects.packages_tree import PackagesTree
|
|
|
|
|
|
logger = logging.getLogger(__package__)
|
|
|
|
|
|
class PackagesForest(object):
|
|
"""Helper class to deal with dependency graph."""
|
|
|
|
def __init__(self):
|
|
self.trees = OrderedDict()
|
|
|
|
def add_tree(self, priority):
|
|
"""Add new tree to end of forest.
|
|
|
|
:return: The added tree
|
|
"""
|
|
|
|
try:
|
|
return self.trees[priority]
|
|
except KeyError:
|
|
tree = self.trees[priority] = PackagesTree()
|
|
return tree
|
|
|
|
def get_packages(self, requirements):
|
|
"""Get the packages according requirements.
|
|
|
|
:param requirements: the list of requirements
|
|
:return list of packages to copy
|
|
"""
|
|
|
|
# TODO(bgaifullin): use versions intersection instead of union
|
|
# now the all versions that fit requirements are selected
|
|
# need to select only one version that fits all requirements
|
|
|
|
resolved = set()
|
|
unresolved = set()
|
|
stack = [(None, requirements)]
|
|
|
|
while stack:
|
|
pkg, requirements = stack.pop()
|
|
for required in requirements:
|
|
for rel in required:
|
|
if rel not in unresolved:
|
|
candidate = self.find(rel)
|
|
if candidate is not None:
|
|
if candidate not in resolved:
|
|
stack.append((candidate, candidate.requires))
|
|
resolved.add(candidate)
|
|
break
|
|
else:
|
|
unresolved.add(required)
|
|
logger.warning("Unresolved relation: %s from %s",
|
|
required, pkg and pkg.name)
|
|
return resolved
|
|
|
|
def find(self, relation):
|
|
"""Finds package in forest.
|
|
|
|
:param relation: the package relation
|
|
:return: the packages from first tree if found otherwise empty list
|
|
"""
|
|
for tree in six.itervalues(self.trees):
|
|
candidate = tree.find(relation.name, relation.version)
|
|
if candidate is not None:
|
|
return candidate
|