core/rebuildlist-order.py

211 lines
5.2 KiB
Python
Raw Normal View History

2010-04-04 20:55:59 +08:00
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# rebuildorder.py - find the order to build a list of packages
#
# Copyright (c) 2010 by Mark Pustjens <pustjens@dds.nl>
# Copyright (c) 2009 by Allan McRae <allan@archlinux.org>
#
# 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, see <http://www.gnu.org/licenses/>.
#
import os, sys, re
from collections import deque
from optparse import OptionParser
def get_pkgs(pkgfile):
try:
f = open(pkgfile)
except IOError:
print "Error: could not read packages file '%s'" % pkgfile
pkgs = set(f.read().split())
return pkgs
def find_pkgbuild(abspath, pkg):
cmd = 'find %s -wholename "*/%s/PKGBUILD" -print' % (abspath, pkg)
files = os.popen(cmd).readlines()
if len(files) >= 1:
return files[0][:-1]
else:
return None
def get_deps_from_pkgbuild(pkgbuild_file):
pkgbuild = open(pkgbuild_file)
deps = set()
makedeps = set()
for line in pkgbuild.readlines():
m = re.match("^depends=\((.*)\)", line)
if m is not None:
for pkg in m.group(1).split(" "):
pkg = pkg.strip("'\"")
pkg = pkg.split(">")[0].split("<")[0].split("=")[0]
deps.add(pkg)
m = re.match("^makedepends=\((.*)\)", line)
if m is not None:
for pkg in m.group(1).split(" "):
pkg = pkg.strip("'\"")
pkg = pkg.split(">")[0].split("<")[0].split("=")[0]
makedeps.add(pkg)
return (deps, makedeps)
def get_deps_from_pacman(package):
deps = set()
pkginfo = os.popen("pacman -Si " + package + " 2> /dev/null").read().split("\n")
if pkginfo != ['']:
found = False
for i in pkginfo:
if not found:
if i[0:10] == "Depends On":
deplist = i.split()[3:]
found = True
else:
if i[0] != " ":
break
deplist += i.split()
for i in deplist:
pkg = i.split(">")[0].split("<")[0].split("=")[0]
deps.add(pkg)
return deps
def bfsa(G, D, R):
"""
BFS Search on graph G, using the set R as the graph roots.
This BFS only visits a node if all its parents have been visisted.
D is the reverse adjacency list representation of G.
Nodes which are part of a cycle are not in the output.
"""
Q = deque(R)
visited = set(R)
while Q:
v = Q.pop()
visited.add(v)
yield v
for w in G[v]:
if w not in visited and (D[w] & visited) == D[w]:
visited.add(w)
Q.appendleft(w)
def main():
usage = "usage: %prog [options] -p <pkgfile>"
parser = OptionParser(usage)
parser.add_option('-p', '--pkg', dest="pkgfile", nargs=1,
metavar="<pkgfile>", help="File containing a list of packages.")
parser.add_option('-c', '--cycles', action="store_true", dest="cycles_only",
help="Show only packages which have circular dependencies.")
parser.add_option('-m', '--makedeps', action="store_true", dest="makedeps",
help="Automatically add makedeps to package list.")
parser.add_option('-a', '--abs', dest="abspath", nargs=1,
help="Path to ABS.")
parser.set_defaults(pkgfile=None, cycles_only=False, makedeps=False, abspath=".")
(options, args) = parser.parse_args()
if options.pkgfile is None:
parser.error("No package file name provided.")
exit(1)
pkgs = get_pkgs(options.pkgfile)
"""
Q is a queue of all packages to be processed
G is the graph of dependencies (i depends on D[i])
D is the graph of requirements (i requires G[i]) (reversed G)
R is the list of graph roots of G
O is the ordered list of packages. Packages on top need to be compiled first.
O does not contain packages with circular dependencies.
"""
Q = deque(pkgs)
G = {}
D = {}
R = []
M = set()
""" Fill G """
while Q:
pkg = Q.pop()
if pkg in G.keys():
continue
pkgbuild = find_pkgbuild(options.abspath, pkg)
"""
Determine all dependencies, including makedeps.
Try abs first and if that fails, use pacman.
This is because only abs includes makedeps.
"""
if pkgbuild is not None:
try:
(deps, makedeps) = get_deps_from_pkgbuild(pkgbuild)
except IOError:
makedeps = set()
deps = get_deps_from_pacman(pkg)
else:
makedeps = set()
deps = get_deps_from_pacman(pkg)
if options.makedeps:
G[pkg] = (deps & pkgs) | makedeps # weed out deps not in the packages list, but keep makedeps
D[pkg] = set()
# we also need to process all makedeps (if requested on commandline)
for makedep in makedeps:
Q.appendleft(makedep)
M.add(makedep)
else:
G[pkg] = deps & pkgs
D[pkg] = set()
""" Fill D """
for pkg in G.keys():
for dep in G[pkg]:
D[dep].add(pkg)
""" Fill R """
R = [key for key in G.keys() if len(G[key]) == 0]
""" Fill O """
O = list(bfsa(D, G, R))
if not options.cycles_only:
""" print O """
for pkg in O:
print pkg
""" print all packages not in O (i.e those whith circular dependencies) """
for pkg in G.keys():
if pkg not in O:
print pkg
if __name__ == "__main__":
main()