Welcome, guest | Sign In | My Account | Store | Cart

port-alldeps package ...
lists all dependencies of Macports packages -- 1st 2nd 3rd level ...
by repeatedly calling "port deps".

Python, 143 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python
"""
port-alldeps package ...
lists all dependencies of Macports packages -- 1st 2nd 3rd level ...
by repeatedly calling "port deps".
Example:
    port deps pandoc ->
        pandoc has build dependencies on:
                ghc
                haddock
        pandoc has library dependencies on:
                gmp

    port-alldeps pandoc ->
        pandoc*    -> ghc haddock* gmp
        ghc        -> readline gmp perl5.8
        haddock*   -> ghc hs-ghc-paths*
        gmp        ->
        readline   -> ncurses
        perl5.8    ->
        hs-ghc-paths* -> ghc
        ncurses    -> ncursesw
        ncursesw   ->

"*" marks packages that are not installed.
Thus port-alldeps | egrep '\*  *->'  lists what has to be built:
    pandoc*    -> ghc haddock* gmp
    haddock*   -> ghc hs-ghc-paths*
    hs-ghc-paths* -> ghc

"port list installed" is run first, which may be slow.
To use a file instead,
    port list installed > date.portsinstalled
    export Macportsinstalled=date.portsinstalled

"""

# breadth-first, not topo sorted
# alldeps-dot | graphviz not so hot, see aewm.png
# "port deps" is slow, one exec per dep is slower:
#   alldeps ImageMagick 14 sec, port -F 9 sec

# http://en.wikipedia.org/wiki/Macports ff

# alternate approach: cache the whole deps graph, 4k nodes
# google "package dependency graph" ...
# really need a short Howto edit portfiles to ignore some deps


#...............................................................................
import os
import subprocess
import sys

__date__ = "4aug 2009"  # 27oct 2008
__author_email__ = "denis-bz-py at t-online dot de"

#...............................................................................
def execv( cmd_arg_list ):
    """ execvp [cmd, args ...] -> [line ...]
    """
    out, err = subprocess.Popen( cmd_arg_list, stdout=subprocess.PIPE, shell=False ) \
        .communicate()  # wait til process ends
    lines = out.split( "\n" )
    if lines:  lines.pop()  # "" after \n at the end
    return lines

def macport( args ):
    """ port args -> [lines]
        (exec each call, slow)
    """
        # <-> one running process ? try Pexpect ?
    return execv( ["port"] + args.split() )  # in $PATH

installed = {}
Macportsinstalled = os.getenv( "Macportsinstalled", None )

def port_installed():
    """ port list installed -> installed[ mod ... ] """
    if Macportsinstalled:
        lines = open( Macportsinstalled ) .readlines()
    else:
        print >>sys.stderr, "running \"port list installed\" ..."
        lines = macport( "list installed" )
    for line in lines:
        mod, version = line.split()[0:2]
        installed[mod] = version
    print "# info: %d ports are installed" % len(installed)

#...............................................................................
def port_deps( mod ):
    """ -> [deps] or [] """
    deps = []
    for line in macport( "deps " + mod ):
        if not (line.endswith( ":" ) \
        or      line.endswith( " has no dependencies" )):
            deps.append( line.strip() )
    return deps

#...............................................................................
def deco( mod ):
    return mod if mod in installed  else (mod + "*")

def print_deps( mod, deps ):
    print "%-10s ->" % deco(mod) ,
    for d in deps:
        print deco(d) ,
    print ""

def alldeps( mod ):
    """ all deps: just iterate port deps ... breadth-first """
    bfs = [mod]
    done = {}
    while bfs:
        mod = bfs.pop( 0 )
        if mod in done:  continue
        deps = port_deps( mod )
        print_deps( mod, deps )
        done[mod] = 1
        bfs.extend( [d for d in deps if d not in done] )


#...............................................................................
roots = ["pandoc"]
if len(sys.argv) > 1:
    if sys.argv[1].startswith( "-" ):  # -h --help
        print __doc__  # at the top
        exit( 1 )
    roots = sys.argv[1:]

try:
    import bz.util
    print bz.util.From()  # date pwd etc.
    bz.util.scan_eq_args( globals() )  # Macportsinstalled= ...
except:
    pass

port_installed()  # port list installed -> installed[ mod ... ]
for root in roots:
    alldeps( root )
    print ""

# end port-alldeps.py

"port install apackage" can lead to an avalanche of installs
if "apackage" requires "anotherpackage", which requires "yetanotherpackage" ...
port-alldeps lists the tree of package dependencies,
for people who (unlike Calvin) like to look, then leap.

2 comments

Charlie Clark 14 years, 7 months ago  # | flag

I find this module pretty confusing and would suggest a class to compartmentalise functionality a bit better. What is the bz module that you conditionally import? If it is only relevant to one method it would be preferrable to have it tied to that method only or at the top of the module.

Writing a function to mimic execv but actually use subprocess makes little sense to me. Although I am not sure if I would actually want to do everything with subprocesses. It should be enough to get the path information and then process the info from the relevant Portfile directly. Personally I'd find some additional information about the ports such as whether they have already been listed (Graphviz has lots of repeated dependencies) or the number of library, build or runtime dependencies.

Please drop the Java ( () ) isms and print >> was never a good idea. Actually I'd be tempted to log to stdout rather than print.

Shalabh Chaturvedi 14 years, 4 months ago  # | flag

A shorter version that I wrote to do just the minimal work - print dependency tree:

# For macports. Print all required ports for specified port in a tree form
# E.g. port-deps-tree.py python26
import subprocess
import sys

dep_line_prefix = 'Library Dependencies:'
cached_deps = dict()

def get_immediate_deps(portname):
    if portname not in cached_deps:
        output = subprocess.Popen(["port","deps", portname], stdout=subprocess.PIPE).communicate()[0]
        lines = output.splitlines()
        for line in lines:
            if line.startswith(dep_line_prefix):
                deps = [d.strip() for d in line[len(dep_line_prefix):].split(',')]
                cached_deps[portname] = deps
                break
        else:
            cached_deps[portname] = []
    return cached_deps[portname]

def get_deps_tree(portname):
    deps = get_immediate_deps(portname)
    deps_tree = [(d, get_deps_tree(d)) for d in deps]
    return deps_tree

def print_deps_tree(deps_tree, indent=0):
    indent_spaces = '  ' * indent
    for dep, subtree in sorted(deps_tree):
        if subtree:
            print '%s%s:' % (indent_spaces, dep)
            print_deps_tree(subtree, indent=indent+1)
        else:
            print '%s%s' % (indent_spaces, dep)

if __name__ == '__main__':
    portname = sys.argv[1]
    deps_tree = get_deps_tree(portname)
    print_deps_tree(deps_tree)

Here is an example:

$ python port-deps-tree.py autoconf
help2man:
  gettext:
    expat
    libiconv
    ncurses
  libiconv
  p5-locale-gettext:
    gettext:
      expat
      libiconv
      ncurses
    libiconv
    perl5:
      perl5.8
  perl5:
    perl5.8
m4
perl5:
  perl5.8