pkgsrc/mk/scripts/depends-depth-first.awk
dbj 06b5331c6d this script was not visiting packages in the proper order when subpackages
had common dependencies.  This change hopefully fixes that.
2009-01-05 02:37:05 +00:00

290 lines
9 KiB
Awk
Executable file

#!/usr/bin/awk -f
#
# $NetBSD: depends-depth-first.awk,v 1.6 2009/01/05 02:37:05 dbj Exp $
#
# Copyright (c) 2006 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Johnny C. Lam.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by the NetBSD
# Foundation, Inc. and its contributors.
# 4. Neither the name of The NetBSD Foundation nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
######################################################################
#
# NAME
# depends-depth-first.awk -- traverse the dependency graph
#
# SYNOPSIS
# depends-depth-first.awk -- [options] pkgpath ...
#
# DESCRIPTION
# depends-depth-first.awk performs a depth-first traversal of the
# dependency graph associated with the package directories specified
# on the command line, and provides a hook to allow a shell command
# line to be executed within each package directory during traversal.
#
# OPTIONS
# The following command line arguments are supported.
#
# -- This is a mandatory option and must always be the
# first option specified.
#
# -c cmdline Execute the specified command line when visiting
# a package directory during traversal. If the -c
# option is not given, then the default action is
# to output the package directory name.
#
# -d depends-type
# When searching for a package's dependencies, use
# the named "depends-type". This is passed as the
# DEPENDS_TYPE argument of the "show-depends-pkgpaths"
# target. By default, the dependency type is "all".
#
# -o order The dependencies are visited during the dependency
# graph traversal in the named order, which is either
# "prefix" or "postfix". By default, dependencies
# are visited in "postfix" order.
#
# -r If the -r option is specified, then the package
# directories specified on the command-line are
# also visited.
#
# -s pkgsrcdir Use the specified directory as the path to the
# pkgsrc directory tree. By default, this is the
# value stored in the PKGSRCDIR environment variable.
#
# ENVIRONMENT
# MAKEFLAGS This contains the shell environment in the format
# required by make(1) that is passed to the process
# that outputs a package's dependencies.
#
# PKGSRCDIR This is the location of the pkgsrc directory tree,
# which defaults to "/usr/pkgsrc".
#
######################################################################
######################################################################
#
# Initialize global variables, parse the command-line arguments, and
# invoke the main() function
#
######################################################################
BEGIN {
ECHO = ENVIRON["ECHO"] ? ENVIRON["ECHO"] : "echo"
MAKE = ENVIRON["MAKE"] ? ENVIRON["MAKE"] : "make"
PKGSRCDIR = ENVIRON["PKGSRCDIR"] ? ENVIRON["PKGSRCDIR"] : "/usr/pkgsrc"
TEST = ENVIRON["TEST"] ? ENVIRON["TEST"] : "test"
self = "depends-depth-first.awk"
cmd_hook = ""
depends_type = "all"
do_root = 0
walk_order = "postfix"
ARGSTART = 1
parse_options()
main()
}
######################################################################
#
# usage()
# Output the usage message to standard error.
#
######################################################################
function usage() {
print "usage: " self " [-- [-c cmdline] [-d depends-type] [-o order] [-r] [-s pkgsrcdir]] [pkgpath ...]" > "/dev/stderr"
}
######################################################################
#
# parse_options()
# Adjust global variables based on the options passed on the
# command line. After this function exists, ARGSTART is set
# to the index in the ARGV array of the first package directory
# in which to begin the traversal.
#
######################################################################
function parse_options( option) {
if (ARGV[ARGSTART] == "--") {
ARGSTART++
}
while (ARGSTART < ARGC) {
option = ARGV[ARGSTART]
if (option == "-c") {
cmd_hook = ARGV[ARGSTART + 1]
ARGSTART += 2
} else if (option == "-d") {
depends_type = ARGV[ARGSTART + 1]
ARGSTART += 2
} else if (option == "-o") {
walk_order = ARGV[ARGSTART + 1]
ARGSTART += 2
} else if (option == "-r") {
do_root = 1
ARGSTART++
} else if (option == "-s") {
PKGSRCDIR = ARGV[ARGSTART + 1]
ARGSTART += 2
} else if (option == "--") {
ARGSTART++
break;
} else if (match(option, /^-.*/) != 0) {
option = substr(option, RSTART + 1, RLENGTH)
print self ": unknown option -- " option > "/dev/stderr"
usage()
exit 1
} else {
ARGSTART++
}
}
if (walk_order !~ /prefix|postfix/) {
print self ": unknown walk order -- " walk_order > "/dev/stderr"
usage()
exit 1
}
}
######################################################################
#
# main()
# This provides an implementation of the well-known non-recursive
# algorithm for depth-first-traversal of a graph, but with a
# small modification to allow visiting the nodes (package directories)
# in either "prefix" or "postfix" order. This more closely mimics
# a function stack than the usual non-recursive DFS algorithm.
#
######################################################################
function main( cmd, depends_pkgpath, dir, pkgpath) {
#
# Push the given package directories onto the stack.
while (ARGC >= ARGSTART) {
ARGC--;
cmd = TEST " -d " PKGSRCDIR "/" ARGV[ARGC]
if (system(cmd) == 0) {
if (do_root == 0) {
root[ARGV[ARGC]] = ARGV[ARGC]
}
if (status[ARGV[ARGC]] == "") {
push(dir_stack, ARGV[ARGC])
}
}
# Depth-first traversal of dependency graph.
while (!empty(dir_stack)) {
pkgpath = top(dir_stack)
if (status[pkgpath] != "") {
if (walk_order == "postfix" && status[pkgpath] != "visited") {
status[pkgpath] = "visited"
visit(pkgpath)
}
pop(dir_stack)
continue
}
status[pkgpath] = "walked"
if (walk_order == "prefix" && status[pkgpath] != "visited") {
status[pkgpath] = "visited"
visit(pkgpath)
}
# Grab the "depends_type" dependencies of the current
# package and push them onto the stack. We use the
# "show-depends-pkgpaths" target to fetch this information.
#
dir = PKGSRCDIR "/" pkgpath
cmd = "if " TEST " -d " dir "; then cd " dir " && " MAKE " show-depends-pkgpaths DEPENDS_TYPE=\"" depends_type "\"; fi"
while (cmd | getline depends_pkgpath) {
if (status[depends_pkgpath] == "") {
push(tmp_stack, depends_pkgpath)
}
}
close(cmd)
# This isn't really necessary, but does preserve child traversal
# order as presented by show-depends-pkgpaths
while (!empty(tmp_stack)) {
push(dir_stack, top(tmp_stack))
pop(tmp_stack)
}
}
}
exit 0
}
######################################################################
#
# visit(pkgpath)
# Visit the package directory by running the shell command
# specified on the command line and stored in "cmd_hook".
# If "cmd_hook" is empty, then just print the package directory
# name.
#
######################################################################
function visit(pkgpath, cmd, dir) {
if ((do_root == 0) && (root[pkgpath] != "")) {
return
}
if (cmd_hook == "") {
print pkgpath
} else {
dir = PKGSRCDIR "/" pkgpath
cmd = "cd " dir " && " cmd_hook
system(cmd)
}
}
######################################################################
#
# empty(stack)
# top(stack)
# push(stack, element)
# pop(stack)
# The well-known functions associated with a STACK.
#
######################################################################
function empty(stack) {
return (stack[0] <= 0)
}
function top(stack) {
return stack[stack[0]];
}
function push(stack, elt) {
stack[++stack[0]] = elt
}
function pop(stack) {
stack[0]--
}