diff packages/pkgconf.tcl @ 0:3111d98ba7b3 ecos-v1_1-release

Initial commit of eCos version 1.1
author jlarmour
date Tue, 11 May 1999 11:16:07 +0000
parents
children 443894e2e912
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/pkgconf.tcl
@@ -0,0 +1,5751 @@
+# {{{  Banner                           
+
+#===============================================================================
+#
+#    pkgconf.tcl
+#
+#    A temporary build system for target-side software.
+#
+#===============================================================================
+#####COPYRIGHTBEGIN####
+#
+# -------------------------------------------
+# The contents of this file are subject to the Cygnus eCos Public License
+# Version 1.0 (the "License"); you may not use this file except in
+# compliance with the License.  You may obtain a copy of the License at
+# http://sourceware.cygnus.com/ecos
+# 
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
+# License for the specific language governing rights and limitations under
+# the License.
+# 
+# The Original Code is eCos - Embedded Cygnus Operating System, released
+# September 30, 1998.
+# 
+# The Initial Developer of the Original Code is Cygnus.  Portions created
+# by Cygnus are Copyright (C) 1998 Cygnus Solutions.  All Rights Reserved.
+# -------------------------------------------
+#
+#####COPYRIGHTEND####
+#===============================================================================
+######DESCRIPTIONBEGIN####
+#
+# Author(s):	bartv
+# Contributors:	bartv
+# Date:		1998-04-10
+# Purpose:      To generate a build tree (aka a configuration directory)
+#               and fill in basic configurability information.
+# Description:
+# Usage:
+#
+#####DESCRIPTIONEND####
+#===============================================================================
+#
+
+# }}}
+
+# {{{  Version check                    
+
+# ----------------------------------------------------------------------------
+# pkgconf.tcl requires at least version 8.0 of Tcl, since it makes use of
+# namespaces. It is possible that some users still have older versions.
+
+if { [info tclversion] < 8.0 } {
+    puts "You are running Tcl version [info tclversion], patchlevel [info patchlevel]"
+    puts "The pkgconf.tcl script requires version 8.0 or later."
+    puts "Tcl is open source software and can be readily obtained via the internet."
+    puts "Good starting places for more information include www.tclconsortium.org"
+    puts "and www.scriptics.com"
+    exit 0
+}
+
+# }}}
+# {{{  Namespace definition             
+
+# ----------------------------------------------------------------------------
+# Namespaces. All code and variables in this script are kept in the namespace
+# "pkgconf". This is not really necessary for stand-alone operation, but if it
+# ever becomes desirable to embed this script in a larger application then
+# using a namespace is a lot easier.
+#
+# As a fringe benefit, all global variables can be declared inside this
+# namespace and initialised.
+#
+
+namespace eval pkgconf {
+
+    # Is this program running on a real operating system or under Windows ?
+    variable windows_host [expr {$tcl_platform(platform) == "windows"}]
+
+    # Is this script running in command-line or GUI mode ? This is determined
+    # in large part by the command used to invoke the script.
+    variable gui_mode 0
+
+    # Keep track of whether or not GUI mode is possible at all. If
+    # TK is not loaded into the current interpreter then only
+    # command-line operation is possible.
+    variable gui_possible 0
+    
+    # Where is the component repository ? The following input sources
+    # are available:
+    # 1) the command line argument --srcdir. This requires
+    #    the arguments to be parsed early on.
+    # 2) the environment variable PKGCONF_COMPONENT_REPOSITORY.
+    # 3) $argv0 should correspond to the location of the pkgconf.tcl
+    #    script.
+    #
+    variable component_repository ""
+    if { [info exists ::env(PKGCONF_COMPONENT_REPOSITORY)] } {
+	set component_repository $::env(PKGCONF_COMPONENT_REPOSITORY)
+    } else {
+	# Care has to be taken not to end up with a spurious /. at the end,
+	# since that would confuse later checks of component_repository != build_tree
+	set component_repository [pwd]
+	if { [file dirname $argv0] != "." } {
+	    set component_repository [file join $component_repository [file dirname $argv0]]
+	}
+    }
+
+    # Where is the build tree ? This can be specified using the command
+    # line argument --builddir, the environment variable PKGCONF_BUILD_TREE,
+    # or it defaults to the current directory (in accordance with the
+    # normal behaviour of configure scripts).
+    variable build_tree ""
+    if { [info exists ::env(PKGCONF_BUILD_TREE)] } {
+	set build_tree $::env(PKGCONF_BUILD_TREE)
+    } else {
+	set build_tree [pwd]
+    }
+
+    # Details of the command line arguments, if any.
+    variable list_targets_arg   0;      # --targets
+    variable list_packages_arg  0;      # --packages
+    variable list_pkgdata_arg   0;      # --pkgdata
+    variable list_flags_arg     0;      # --flags
+    variable no_windows_arg     0;      # --nw
+    variable target_arg         "";     # --target=abc
+    variable platform_arg       "";     # --platform=sim
+    variable startup_arg        "";     # --platform=rom
+    variable prefix_arg         "";     # --prefix=/tmp/install
+    variable disable_args       "";     # --disable-uitron
+                                        # This is a list of all disabled packages.
+    variable enable_args        "";     # --enable-uitron, as above.
+    array set version_args {};          # --kernel_version=v0.3
+                                        # This is an array indexed by package name.
+    array set cflags_args {};           # DBGFLAGS="-Wall -Werror"
+                                        # This is an array indexed by flag name.
+    variable force_arg 0;               # Forcibly overwrite configuration files.
+    variable defaults_arg 0;            # Do not use the values from the save file.
+    variable use_links_arg 0;           # Make use of symbolic links.
+    variable use_copies_arg 0;          # The inverse.
+    
+    # Details of all known packages. These come out of the packages file.
+    # The variable known_packages is a simple list, and the rest of the
+    # data is held in an array package_data().
+    variable known_packages ""
+    array set package_data {};
+
+    # Details of all known targets, as supplied by the targets file.
+    # The variable known_targets is a simple list, and the rest of the
+    # data is held in an array target_data.
+    variable known_targets ""
+    array set target_data {};
+
+    # Details of all the compiler flags that can be used within the
+    # build system. 
+    variable known_compiler_flags {
+	ARCHFLAGS  CARCHFLAGS  CXXARCHFLAGS  LDARCHFLAGS
+	ERRFLAGS   CERRFLAGS   CXXERRFLAGS   LDERRFLAGS
+	LANGFLAGS  CLANGFLAGS  CXXLANGFLAGS  LDLANGFLAGS
+	DBGFLAGS   CDBGFLAGS   CXXDBGFLAGS   LDDBGFLAGS
+	EXTRAFLAGS CEXTRAFLAGS CXXEXTRAFLAGS LDEXTRAFLAGS
+    }
+
+    # Details of the current configuration values. It is convenient
+    # to store these in an array as well. The array gets filled in by
+    # pkgconf::setup_defaults
+    array set config_data {};
+
+    # Contents of the current save file, if any.
+    array set saved_data {};
+
+    # These variables are used by the code that creates the build tree
+    variable inc_dirs;                  # For each package with exported header
+                                        # files, the path to those header files
+                                        # relative to either the component repository
+                                        # or the build tree.
+    variable tests_dirs;                # Ditto for package tests directories.
+    variable src_dirs;                  # Ditto for src directories.
+
+    variable install_inc_dirs;          # Names of all the directories that should
+                                        # exist below install/include, including pkgconf.
+    variable install_inc_files;         # Ditto for all the files, but excluding
+                                        # ones in pkgconf.
+    variable pkgconf_files;             # Names of all the configuration headers
+    variable install_test_dirs;         # What directories should be created in install/tests
+    
+    # What routines should be invoked for outputting fatal errors and
+    # for warning messages ?
+    variable fatal_error_handler pkgconf::cli_fatal_error
+    variable warning_handler     pkgconf::cli_warning
+    variable report_handler      pkgconf::cli_report
+}
+
+# }}}
+# {{{  Infrastructure                   
+
+# ----------------------------------------------------------------------------
+# Minimal infrastructure support.
+#
+# There must be some way of reporting fatal errors, of outputting warnings,
+# and of generating report messages. The implementation of these things
+# obviously depends on whether or not TK is present. In addition, if this
+# script is being run inside a larger application then that larger
+# application must be able to install its own versions of the routines.
+#
+# Once it is possible to report fatal errors, an assertion facility becomes
+# feasible.
+#
+
+#
+# These routines output fatal errors, warnings or miscellaneous messages.
+# Their implementations depend on the mode in which this script is operating.
+#
+proc pkgconf::fatal_error { msg } {
+    $pkgconf::fatal_error_handler "$msg"
+}
+
+proc pkgconf::warning { msg } {
+    $pkgconf::warning_handler "$msg"
+}
+
+proc pkgconf::report { msg } {
+    $pkgconf::report_handler "$msg"
+}
+
+#
+# Command line versions.
+# NOTE: some formatting so that there are linebreaks at ~72 columns would be
+# a good idea.
+#
+proc pkgconf::cli_fatal_error_handler { msg } {
+    puts "pkgconf fatal error: $msg"
+    exit 1
+}
+
+proc pkgconf::cli_warning_handler { msg } {
+    puts "pkgconf warning: $msg"
+}
+
+proc pkgconf::cli_report_handler { msg } {
+    puts "$msg"
+}
+
+#
+# Determine the default destination for warnings and for fatal errors.
+# In GUI mode message boxes should be used, otherwise the text should just
+# go to stdout (stderr is more dubious).
+#
+# This routine gets invoked before any command line arguments have
+# been processed, to set up a sensible default. It gets invoked again
+# afterwards, since there may have been a change between GUI and CLI
+# mode.
+#
+# After the first call to this function it is possible to use assertions.
+#
+proc pkgconf::initialise_error_handling { } {
+
+    if { $pkgconf::gui_mode } {
+	set pkgconf::fatal_error_handler pkgconf::gui_fatal_error_handler
+	set pkgconf::warning_handler     pkgconf::gui_warning_handler
+	set pkgconf::report_handler      pkgconf::gui_report_handler
+    } else {
+	set pkgconf::fatal_error_handler pkgconf::cli_fatal_error_handler
+	set pkgconf::warning_handler     pkgconf::cli_warning_handler
+	set pkgconf::report_handler      pkgconf::cli_report_handler
+    }
+}
+
+#
+# These routines can be used by containing programs to provide their
+# own error handling.
+#
+proc pkgconf::set_fatal_error_handler { fn } {
+    ASSERT { $fn != "" }
+    set pkgconf::fatal_error_handler $fn
+}
+
+proc pkgconf::set_warning_handler { fn } {
+    ASSERT { $fn != "" }
+    set pkgconf::warning_handler $fn
+}
+
+proc pkgconf::set_report_handler { fn } {
+    ASSERT { $fn != "" }
+    set pkgconf::report_handler $fn
+}
+
+#
+# A very simple assertion facility. It takes a single argument, an expression
+# that should be evaluated in the calling function's scope, and on failure it
+# should generate a fatal error.
+#
+proc pkgconf::ASSERT { condition } {
+
+    set result [uplevel 1 [list expr $condition]]
+    
+    if {  $result == 0 } {
+	fatal_error "Assertion failure.\nPredicate \"$condition\"\nThe failure occurred in \"[info level -1]\""
+    }
+}
+
+# }}}
+# {{{  Utilities                        
+
+# ----------------------------------------------------------------------------
+# cdl_compare_version. This is a partial implementation of the full
+# cdl_compare_version facility defined in the product specification. Its
+# purpose is to order the various versions of a given package with
+# the most recent version first. As a special case, "current" is
+# always considered the most recent.
+#
+# There are similarities between cdl_compare_version and with Tcl's
+# package vcompare, but cdl_compare_version is more general.
+#
+
+proc pkgconf::cdl_compare_version { arg1 arg2 } {
+
+    if { $arg1 == $arg2 } {
+	return 0
+    }
+    if { $arg1 == "current"} {
+	return -1
+    }
+    if { $arg2 == "current" } {
+	return 1
+    }
+
+    set index1 0
+    set index2 0
+    set ch1    ""
+    set ch2    ""
+    set num1   ""
+    set num2   ""
+    
+    while { 1} {
+
+	set ch1 [string index $arg1 $index1]
+	set ch2 [string index $arg2 $index2]
+	set num1 ""
+	set num2 ""
+
+	if { ($ch1 == "") && ($ch2 == "") } {
+	    
+	    # Both strings have terminated at the same time. There may have
+	    # been some spurious leading zeroes in numbers.
+	    return 0
+	    
+	} elseif { $ch1 == "" } {
+
+	    # The first string has ended first. If ch2 is a separator then
+	    # arg2 is a derived version, e.g. v0.3.p1 and hence newer. Otherwise ch2
+	    # is an experimental version v0.3beta and hence older.
+	    if { [string match \[-._\] $ch2] } {
+		return 1
+	    } else {
+		return -1
+	    }
+	} elseif { $ch2 == "" } {
+
+	    # Equivalent to the above.
+	    if { [string match \[-._\] $ch1] } {
+		return -1
+	    } else {
+		return 1
+	    }
+	}
+
+	# There is still data to be processed.
+	# Check for both strings containing numbers at the current index.
+	if { ( [string match \[0-9\] $ch1] ) && ( [string match \[0-9\] $ch2] ) } {
+
+	    # Extract the entire numbers from the version string.
+	    while { [string match \[0-9\] $ch1] } {
+		set  num1 "$num1$ch1"
+		incr index1
+		set  ch1 [string index $arg1 $index1]
+	    }
+	    while { [string match \[0-9\] $ch2] } {
+		set  num2 "$num2$ch2"
+		incr index2
+		set ch2 [string index $arg2 $index2]
+	    }
+
+	    if { $num1 < $num2 } {
+		return 1
+	    } elseif { $num1 > $num2 } {
+		return -1
+	    }
+	    continue
+	}
+
+	# This is not numerical data. If the two characters are the same then
+	# move on.
+	if { $ch1 == $ch2 } {
+	    incr index1
+	    incr index2
+	    continue
+	}
+	
+	# Next check if both strings are at a separator. All separators can be
+	# used interchangeably.
+	if { ( [string match \[-._\] $ch1] ) && ( [string match \[-._\] $ch2] ) } {
+	    incr index1
+	    incr index2
+	    continue
+	}
+
+	# There are differences in the characters and they are not interchangeable.
+	# Just return a standard string comparison.
+	return [string compare $ch1 $ch2]
+    }
+}
+
+if { 0 } {
+
+    puts "compare current  v0.1     : [pkgconf::cdl_compare_version current v0.1]"
+    puts "compare v0.1     current  : [pkgconf::cdl_compare_version v0.1 current]"
+    puts "compare current  current  : [pkgconf::cdl_compare_version current current]"
+    puts "compare v0.1     v0.1     : [pkgconf::cdl_compare_version v0.1 v0.1]"
+    puts "compare v0_1     v0.1     : [pkgconf::cdl_compare_version v0_1 v0.1]"
+    puts "compare v0.1     v0_1     : [pkgconf::cdl_compare_version v0.1 v0_1]"
+    puts "compare v0_1     v0-1     : [pkgconf::cdl_compare_version v0_1 v0-1]"
+    puts "compare v0-1     v0_1     : [pkgconf::cdl_compare_version v0-1 v0_1]"
+    puts "compare v0.2     v0.1     : [pkgconf::cdl_compare_version v0.2 v0.1]"
+    puts "compare v0.1     v0.2     : [pkgconf::cdl_compare_version v0.1 v0.2]"
+    puts "compare v0.10    v0.2     : [pkgconf::cdl_compare_version v0.10 v0.2]"
+    puts "compare v0.2     v0.10    : [pkgconf::cdl_compare_version v0.2 v0.10]"
+    puts "compare 980410   980412   : [pkgconf::cdl_compare_version 980410 980412]"
+    puts "compare 980412   980410   : [pkgconf::cdl_compare_version 980412 980410]"
+    puts "compare ss980410 ss980412 : [pkgconf::cdl_compare_version ss980410 ss980412]"
+    puts "compare ss980412 ss980410 : [pkgconf::cdl_compare_version ss980412 ss980410]"
+    puts "compare v0.1.2   v0.1.3   : [pkgconf::cdl_compare_version v0.1.2 v0.1.3]"
+    puts "compare v0.1.3   v0.1.2   : [pkgconf::cdl_compare_version v0.1.3 v0.1.2]"
+    puts "compare v1.0     v0.1.3   : [pkgconf::cdl_compare_version v1.0 v0.1.3]"
+    puts "compare v0.1.3   v1.0     : [pkgconf::cdl_compare_version v0.1.3 v1.0]"
+    puts "compare v1.0beta v1.0     : [pkgconf::cdl_compare_version v1.0beta v1.0]"
+    puts "compare v1.0     v1.0beta : [pkgconf::cdl_compare_version v1.0 v1.0beta]"
+    puts "compare v1.0.p1  v1.0     : [pkgconf::cdl_compare_version v1.0.p1 v1.0]"
+    puts "compare v1.0     v1.0.p1  : [pkgconf::cdl_compare_version v1.0 v1.0.p1]"
+}
+
+# }}}
+# {{{  Argument parsing                 
+
+# ----------------------------------------------------------------------------
+# Parsing the arguments. The following arguments should be accepted for
+# backwards compatibility reasons.
+#
+#     --target=<arch_name>
+#     --platform=sim
+#     --startup=rom     or   --startup=ram
+#     --prefix=<directory>
+#     --disable-uitron
+#     --disable-libc
+#
+# There was also a --disable-kernel_c_api, but that is no longer supported.
+# For now disabling only applies to entire packages, not to components
+# within a package. The argument --release=debug has also been disabled
+# because it affects too many things (the makevars file, various #define's,
+# etc.).
+#
+# Also it was possible to use --disable-uitron=yes, but that was
+# undocumented and is not worth supporting.
+#
+# Configure allowed --srcdir=xxx, although it did not need it. It is useful
+# to support this, and to add --builddir. --installdir as a prefix alias
+# then makes sense.
+#
+# --help should be supported if present as a single argument.
+#
+# It is desirable to support additional arguments of the form:
+#
+#     --enable-uitron           (to undo a disable in the saved data)
+#     --kernel-version=current
+#     --uitron-version=v0.3
+#
+# Similarly it is desirable to allow users to control compiler flags, e.g.
+#     DBGFLAGS="-Wall -Werror"
+#
+# A new argument is --nw, to disable the GUI environment. Other new
+# arguments are:
+#   --targets and --packages to display the appropriate details
+#   --force to force files to be copied across from the repository
+#   --defaults to cause the program to ignore the values in the save file.
+#   --linkhdrs and --copyhdrs to control how header files should be handled.
+#
+# It is also a good idea to be a bit more flexible about the format
+# of the command line arguments, for example -target=xxx should work,
+# as should -target xxx
+#
+# The argv0 argument should be the name of this script. It can be used
+# to get at the component repository location. If this script has been
+# run incorrectly then currently it will fail: in future it may be
+# desirable to check an environment variable instead.
+#
+# The argv argument is a string containing the rest of the arguments.
+# If any of the arguments contain spaces then this argument will be
+# surrounded by braces. If any of the arguments contain braces then
+# things will break.
+#
+
+proc pkgconf::parse_arguments { argv0 argv } {
+
+    # First, check for --help or any of the variants. If this script
+    # is running in a larger program then it is assumed that the
+    # containing program will not pass --help as an argument.
+    if { ( $argv == "--help" ) || ( $argv == "-help" ) ||
+         ( $argv == "--H"    ) || ( $argv == "-H" ) } {
+
+	 argument_help
+	 exit 0
+    }
+
+    if { $argv != "" } {
+
+	# There are arguments. If any of the arguments contained
+	# spaces then these arguments will have been surrounded
+	# by braces, which is a nuisance. So start by turning the
+	# arguments into a numerically indexed array.
+
+	set argc 0
+	array set args { }
+	foreach arg $argv {
+	    set args([incr argc]) $arg
+	}
+
+	# Now examine each argument with regular expressions. It is
+	# useful to have some variables filled in by the regexp
+	# matching.
+	set dummy  ""
+	set match1 ""
+	set match2 ""
+	for { set i 1 } { $i <= $argc } { incr i } {
+
+	    # Check for -nw. The regexp allows an optional -, followed by
+	    # the text -nw, and nothing else.
+	    if { [regexp -- {^-?-nw$} $args($i)] == 1 } {
+	        set pkgconf::no_windows_arg 1
+		continue
+	    }
+
+	    # Check for --packages and the other simple ones.
+	    if { [regexp -- {^-?-packages$} $args($i)] == 1 } {
+		set pkgconf::list_packages_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-pkgdata$} $args($i)] == 1 } {
+		set pkgconf::list_pkgdata_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-targets$} $args($i)] == 1 } {
+		set pkgconf::list_targets_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-flags$} $args($i)] == 1 } {
+		set pkgconf::list_flags_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-force$} $args($i)] == 1 } {
+		set pkgconf::force_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-defaults$} $args($i)] == 1 } {
+		set pkgconf::defaults_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-linkhdrs$} $args($i)] == 1 } {
+		set pkgconf::use_links_arg 1
+		continue
+	    }
+	    if { [regexp -- {^-?-copyhdrs$} $args($i)] == 1 } {
+		set pkgconf::use_copies_arg 1
+		continue
+	    }
+
+	    # Now for the directories. srcdir and builddir will have
+	    # been calculated already, but may be overridden here.
+	    if { [regexp -- {^-?-src_?dir=?(.*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::component_repository $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --srcdir"
+		    } else {
+			set pkgconf::component_repository $args([incr i])
+		    }
+		}
+		continue
+	    }
+	    if { [regexp -- {^-?-build_?dir=?(.*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::build_tree $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --builddir"
+		    } else {
+			set pkgconf::build_tree $args([incr i])
+		    }
+		}
+		continue
+	    }
+	    
+	    # And prefix, before moving on to the more interesting ones.
+	    # Prefix is a pathname so spaces are legitimate, as are dots,
+	    # directory separators, and drive indicators.
+	    #
+	    # NOTE: strictly speaking the pattern should be platform specific.
+	    if { [regexp -- {^-?-prefix=?(.*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::prefix_arg $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --prefix"
+		    } else {
+			set pkgconf::prefix_arg $args([incr i])
+		    }
+		}
+		continue
+	    }
+
+	    # --installdir is a useful alias for prefix.
+	    if { [regexp -- {^-?-install_?dir=?(.*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::prefix_arg $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --installdir"
+		    } else {
+			set pkgconf::prefix_arg $args([incr i])
+		    }
+		}
+		continue
+	    }
+	    
+
+	    # Now check for a --target arg. The -target must match exactly,
+	    # but there can be one leading hyphen that gets ignored. The
+	    # target can be part of the same argument (with an = in between)
+	    # or it can be all of the next argument.
+	    #
+	    # This code will match spuriously with --targetxxx, i.e. the
+	    # = is optional. This is harmless.
+	    #
+	    # The target is not validated at this point, that happens after
+	    # reading in the targets file. It is possible to specify
+	    # several --target arguments, and only the last one takes
+	    # effect.
+	    if { [regexp -- {^-?-target=?([_a-zA-Z0-9]*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::target_arg $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --target"
+		    } else {
+			set pkgconf::target_arg $args([incr i])
+		    }
+		}
+		continue
+	    }
+
+	    # Ditto for --platform. 
+	    if { [regexp -- {^-?-platform=?([_a-zA-Z0-9]*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::platform_arg $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --platform"
+		    } else {
+			set pkgconf::platform_arg $args([incr i])
+		    }
+		}
+		continue
+	    }
+
+	    # And for --startup.
+	    if { [regexp -- {^-?-startup=?([_a-zA-Z0-9]*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    set pkgconf::startup_arg $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --startup"
+		    } else {
+			set pkgconf::startup_arg $args([incr i])
+		    }
+		}
+		continue
+	    }
+
+	    # Now for --disable-<package>. The disabled packages are stored
+	    # in a simple list.
+	    if { [regexp -- {^-?-disable-?=?([_a-zA-Z0-9]*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    lappend pkgconf::disable_args $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --disable"
+		    } else {
+			lappend pkgconf::disable_args $args([incr i])
+		    }
+		}
+		continue
+	    }
+	    
+	    # The same for --enable-<package>.
+	    if { [regexp -- {^-?-enable-?=?([_a-zA-Z0-9]*)$} $args($i) dummy match1] == 1 } {
+		if { $match1 != "" } {
+		    lappend pkgconf::enable_args $match1
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --enable"
+		    } else {
+			lappend pkgconf::enable_args $args([incr i])
+		    }
+		}
+		continue
+	    }
+
+	    # And for --<package>-version=xxx
+	    # These versions are stored in an array indexed by the
+	    # package string.
+	    if {[regexp -- {^-?-([_a-zA-Z0-9]+)-?version=?([_\.a-zA-Z0-9]*)$} $args($i) dummy match1 match2] == 1} {
+		if { $match2 != "" } {
+		    set pkgconf::version_args($match1) $match2
+		} else {
+		    if { $i == $argc } {
+			fatal_error "pkgconf: missing argument after --$match1-version"
+		    } else {
+			set pkgconf::version_args($match1) $args([incr i])
+		    }
+		}
+		continue
+	    }
+
+	    # For each of the known compiler flags, check for a definition of
+	    # this flag. The flags are stored in an array cflags_args().
+	    # Note that an empty string is legal, so that the default flags
+	    # from the targets file can be disabled.
+	    set its_a_flag 0
+	    foreach flag $pkgconf::known_compiler_flags {
+		if { [regexp -- "^[set flag]=(.*)\$" $args($i) dummy match1] == 1 } {
+		    set pkgconf::cflags_args($flag) $match1
+		    set its_a_flag 1
+		    break
+		}
+	    }
+	    if { $its_a_flag != 0 } {
+		continue
+	    }
+	    
+	    # An unrecognised argument.
+	    fatal_error "pkgconf: invalid argument $args($i)"
+	}
+    } 
+
+    # Under Windows it is desirable to do some checking on any directories that
+    # have been provided. Some cygwin pathnames that a user might supply are
+    # not acceptable to Tcl.
+    if { $pkgconf::windows_host } {
+	set pkgconf::component_repository [get_pathname_for_tcl $pkgconf::component_repository]
+	set pkgconf::build_tree           [get_pathname_for_tcl $pkgconf::build_tree]
+	if { $pkgconf::prefix_arg != "" } {
+	    set pkgconf::prefix_arg [get_pathname_for_tcl $pkgconf::prefix_arg]
+	}
+    }
+    
+    # Enabling this facilitates testing of the parsing code.
+    if { 0 } {
+	puts "component repository is    $pkgconf::component_repository"
+	puts "build tree is              $pkgconf::build_tree"
+	puts "prefix_arg is              $pkgconf::prefix_arg"
+	puts "list_targets_arg is        $pkgconf::list_targets_arg"
+	puts "list_packages_arg is       $pkgconf::list_packages_arg"
+	puts "list_pkgdata_arg is        $pkgconf::list_pkgdata_arg"
+	puts "list_flags_arg is          $pkgconf::list_flags_arg"
+	puts "no_windows_arg is          $pkgconf::no_windows_arg"
+	puts "target_arg is              $pkgconf::target_arg"
+	puts "platform_arg is            $pkgconf::platform_arg"
+	puts "startup_arg is             $pkgconf::startup_arg"
+	puts "disable_args is            $pkgconf::disable_args"
+	puts "enable_args is             $pkgconf::enable_args"
+	puts "force_arg is               $pkgconf::force_arg"
+	puts "defaults_arg is            $pkgconf::defaults_arg"
+	puts "use_links_arg is           $pkgconf::use_links_arg"
+	puts "use_copies_arg is          $pkgconf::use_copies_arg"
+	foreach i [array names pkgconf::version_args] {
+	    puts "Package $i needs version $pkgconf::version_args($i)"
+	}
+	foreach flag [array names pkgconf::cflags_args] {
+	    puts "Compiler flag $flag has value $pkgconf::cflags_args($flag)"
+	}
+	exit 0
+    }
+    
+}
+
+#
+# Display help information if the user has typed --help, -H, --H, or -help.
+# The help text uses two hyphens for consistency with configure.
+# Arguably this should change.
+#
+# Not all legal command line arguments are listed. Some of the command
+# line arguments like --flags are intended for internal use only.
+#
+# NOTE: if this command is being invoked from a shell script that sets
+# the defaults for a particular architecture then the information will not
+# be completely accurate. There is no easy solution for this problem.
+#
+# NOTE: it would be a really good idea to give an example command line.
+# Unfortunately this command line must include a target architecture,
+# and there is no sensible default for that.
+#
+proc pkgconf::argument_help { } {
+
+    puts "Usage: pkgconf \[options\]"
+    puts "Available options are:"
+    puts "  --help               : display this text."
+    puts "  --packages           : list all packages."
+    puts "  --targets            : list all possible targets."
+    puts "  --target=<arch>      : target the specified architecture."
+    puts "  --platform=sim       : target the simulator rather than hardware."
+    puts "  --startup=rom        : generate code suitable for ROMming."
+    puts "  --disable-<pkg>      : disable a given package."
+    puts "  --enable-<pkg>       : enable a given package."
+    puts "  --<pkg>-version=v0.3 : use a specific version of a package."
+    puts "  --defaults           : use default settings instead of the save file."
+    puts "  --srcdir=<dir>       : specify location of the component repository."
+    puts "  --builddir=<dir>     : specify location of the build directory."
+    puts "  --prefix=<dir>       : specify location of the install directory."
+}
+
+# }}}
+# {{{  Packages file                    
+
+# ----------------------------------------------------------------------------
+# The component repository should contain a file "packages" containing details
+# of all supported packages. This file is actually an executable Tcl script
+# containing instances of the "package" command, which takes three arguments.
+# It is necessary to update the variables pkgconf::known_packages and
+# pkgconf::package_data.
+#
+
+proc pkgconf::read_packages { } {
+
+    ASSERT { $pkgconf::component_repository != "" }
+    ASSERT { [array names pkgconf::package_data] == "" }
+    ASSERT { $pkgconf::known_packages == "" }
+
+    # A safe interpreter is used to process the packages file.
+    # This is somewhat overcautious, but it is also harmless.
+    # The following two commands are made accessible to the slave
+    # interpreter and are responsible for updating the actual data.
+    proc set_package_data { name value } {
+	set pkgconf::package_data($name) $value
+    }
+    proc add_known_package { name } {
+	lappend pkgconf::known_packages $name
+    }
+
+    # Create the parser, add the aliased commands, and then define
+    # the routines that do the real work.
+    set parser [interp create -safe]
+    $parser alias set_package_data  pkgconf::set_package_data
+    $parser alias add_known_package pkgconf::add_known_package
+    
+    $parser eval {
+	
+	set current_package ""
+	
+	proc package { name body } {
+	    
+	    add_known_package                   $name
+	    set_package_data "$name,alias"      $name
+	    set_package_data "$name,dir"        ""
+	    set_package_data "$name,versions"   ""
+	    set_package_data "$name,default"    0
+	    set_package_data "$name,incdir"     ""
+	    set_package_data "$name,hardware"   0
+
+	    # Evaluate the body such that the commands know what the current package is.
+	    set ::current_package $name
+	    eval $body
+	    set ::current_package ""
+	}
+
+	proc alias { str } {
+	    set_package_data "$::current_package,alias" $str
+	}
+
+	# The remaining commands are basically clones of alias.
+	proc directory { dir } {
+	    set_package_data "$::current_package,dir" $dir
+	}
+
+	proc default_value { val } {
+	    set_package_data "$::current_package,default" $val
+	}
+
+	proc include_dir { dir } {
+	    set_package_data "$::current_package,incdir" $dir
+	}
+
+	proc hardware { } {
+	    set_package_data "$::current_package,hardware" 1
+	}
+    }
+
+    # The parser is ready to evaluate the script. To avoid having to give the
+    # safe interpreter file I/O capabilities, the file is actually read in
+    # here and then evaluated.
+    set filename [file join $pkgconf::component_repository "packages"]
+    set status [ catch {
+	set fd [open $filename r]
+	set script [read $fd]
+	close $fd
+	$parser eval $script
+    } message]
+
+    if { $status != 0 } {
+	pkgconf::fatal_error "Unexpected error while processing $filename\n$message"
+    }
+
+    # The interpreter and the aliased commands are no longer required.
+    rename set_package_data {}
+    rename add_known_package {}
+    interp delete $parser
+	
+    # At this stage the packages file has been read in. It is a good idea to
+    # check that all of these packages are present and correct, and incidentally
+    # figure out which versions are present.
+    foreach pkg $pkgconf::known_packages {
+	
+	set pkgdir [file join $pkgconf::component_repository $pkgconf::package_data($pkg,dir)]
+	if { ![file exists $pkgdir] || ![file isdir $pkgdir] } {
+	    fatal_error \
+"The file [file join $pkgconf::component_repository "packages"] lists a package\
+$pkg which should be present in $pkgdir. This package is missing."
+	}
+
+	# Each subdirectory should correspond to a release. A utility routine
+	# is available for this.
+	set pkgconf::package_data($pkg,versions) [locate_subdirs $pkgdir]
+	if { $pkgconf::package_data($pkg,versions) == "" } {
+	    fatal_error "The package $pkg does not contain any version directories."		    
+	}
+
+	# Sort all the versions using a version-aware comparison version
+	set pkgconf::package_data($pkg,versions) [
+	    lsort -command pkgconf::cdl_compare_version $pkgconf::package_data($pkg,versions)
+	]
+    }
+
+    # Handle the --packages argument.
+    if { $pkgconf::list_packages_arg != 0 } {
+
+	# The user wants to know about all the arguments. Just to be fancy
+	# this code does some alignment.
+	set name_maxlen 0
+	
+	foreach pkg $pkgconf::known_packages {
+	    if { [string length $pkg] > $name_maxlen} {
+		set name_maxlen [string length $pkg]
+	    }
+	}
+	
+	puts "Known packages are:"
+	foreach pkg $pkgconf::known_packages {
+	    puts "[format "  %-*s : " $name_maxlen $pkg]"
+	    puts "    Available versions: $pkgconf::package_data($pkg,versions)"
+	    puts "    Aliases           : $pkgconf::package_data($pkg,alias)"
+	    if { $pkgconf::package_data($pkg,hardware) } {
+		puts "    This package is only available on specific hardware."
+	    }
+	    if { $pkgconf::package_data($pkg,default) } {
+		puts "    This package is enabled by default."
+	    } else  {
+		puts "    This package is disabled by default."
+	    }
+	}
+    }
+
+    # Ditto for the --pkgdata argument. In this case the data is intended for use
+    # by other programs, and is not necessarily intelligible to users.
+    if { $pkgconf::list_pkgdata_arg != 0 } {
+	foreach pkg $pkgconf::known_packages {
+	    set dir [file join $pkgconf::package_data($pkg,dir) [lindex $pkgconf::package_data($pkg,versions) 0]]
+	    set dir [file nativename $dir]
+	    if { $pkgconf::windows_host != 0 } {
+		regsub -all {\\} $dir {\\\\} dir
+	    }
+	    set alias [get_build_alias $pkg]
+	    puts "\"$pkg\" \"$alias\" \"$dir\""
+	}
+    }
+}
+
+#
+# Given a package name as supplied by the user, return the internal package name.
+# This involves searching through the list of aliases.
+#
+proc pkgconf::find_package { name } {
+
+    foreach pkg $pkgconf::known_packages {
+
+	if { [string toupper $pkg] == [string toupper $name] } {
+	    return $pkg
+	}
+
+	foreach alias $pkgconf::package_data($pkg,alias) {
+	    if { [string toupper $alias] == [string toupper $name] } {
+		return $pkg
+	    }
+	}
+    }
+
+    return ""
+}
+
+#
+# Given a package name, return the first alias string. This is useful for
+# output code.
+#
+proc pkgconf::get_package_alias { name } {
+
+    pkgconf::ASSERT { [lsearch -exact $pkgconf::known_packages $name] != -1 }
+    set alias [lindex $pkgconf::package_data($name,alias) 0]
+    return $alias
+}
+
+#
+# Does a given package actually exist ? This copes with aliases
+# as well.
+proc pkgconf::is_package { name } {
+
+    ASSERT { $name != "" }
+
+    set result 0
+    set name [find_package $name]
+    if { $name != "" } {
+	set result 1
+    }
+    return $result
+}
+
+#
+# Is a particular package a hardware-specific package ? This code
+# deals with aliases as well
+proc pkgconf::is_hardware_package { name } {
+
+    ASSERT { $name != "" }
+
+    set name [find_package $name]
+    ASSERT { $name != "" }
+
+    set result 0
+    if { $pkgconf::package_data($name,hardware) != 0 } {
+	set result 1
+    }
+    return $result
+}
+
+#
+# Is a particular version string valid for the given package ?
+proc pkgconf::is_valid_version { pkg version } {
+
+    ASSERT { $pkg     != "" }
+    ASSERT { $version != "" }
+
+    set pkg [find_package $pkg]
+    ASSERT { $pkg != "" }
+
+    set result 0
+    set index [lsearch -exact $pkgconf::package_data($pkg,versions) $version]
+    if { $index != -1 } {
+	set result 1
+    }
+    return $result
+}
+
+# }}}
+# {{{  Targets file                     
+
+# ----------------------------------------------------------------------------
+# The handling of the targets file is similar to that for the packages file,
+# but a targets file is a bit more complicated.
+#
+
+proc pkgconf::read_targets { } {
+    
+    ASSERT { $pkgconf::component_repository != "" }
+    ASSERT { [array names pkgconf::target_data] == "" }
+    ASSERT { $pkgconf::known_targets == "" }
+    ASSERT { $pkgconf::known_compiler_flags != "" }
+    
+    proc add_known_target { name } {
+	
+	lappend pkgconf::known_targets $name
+	set pkgconf::target_data($name,alias)           $name
+	set pkgconf::target_data($name,prefix)          ""
+	set pkgconf::target_data($name,known_platforms) ""
+	set pkgconf::target_data($name,packages)        ""
+    }
+    proc add_known_platform { arch name } {
+	lappend pkgconf::target_data($arch,known_platforms) $name
+	set pkgconf::target_data($arch,$name,alias)         ""
+	set pkgconf::target_data($arch,$name,startup)       ""
+	set pkgconf::target_data($arch,$name,packages)      ""
+    }
+    proc set_target_data { name value } {
+	set pkgconf::target_data($name) $value
+    }
+
+    set parser [interp create -safe]
+    interp share {} stdout $parser
+    $parser alias set_target_data               pkgconf::set_target_data
+    $parser alias add_known_target              pkgconf::add_known_target
+    $parser alias add_known_platform            pkgconf::add_known_platform
+    $parser eval [list set known_compiler_flags $pkgconf::known_compiler_flags]
+
+    $parser eval {
+
+	set current_target   ""
+	set current_platform ""
+
+	proc target { name body } {
+
+	    add_known_target $name
+	    
+	    # Evaluate the body such that the commands know what the current target is.
+	    set ::current_target $name
+	    eval $body
+	    set ::current_target ""
+	}
+	
+	proc alias { str } {
+
+	    # If processing a platform body, update the platform data. Otherwise update the
+	    # target data. There should be only one alias command per body.
+	    if { $::current_platform != "" } {
+		set_target_data "$::current_target,$::current_platform,alias" $str
+	    } else {
+		set_target_data "$::current_target,alias" $str
+	    }
+	}
+	
+	proc command_prefix { str } {
+	    set_target_data "$::current_target,prefix" $str
+	}
+
+	proc packages { list } {
+	    if { $::current_platform != "" } {
+		set_target_data "$::current_target,$::current_platform,packages" $list
+	    } else {
+		set_target_data "$::current_target,packages" $list
+	    }
+	}
+	
+	proc hal { dir } {
+	    if { $::current_platform != "" } {
+		set_target_data "$::current_target,$::current_platform,haldir" $dir
+	    } else {
+		set_target_data "$::current_target,haldir" $dir
+	    }
+	}
+	
+	proc platform { name body } {
+
+	    add_known_platform $::current_target $name
+	    
+	    set ::current_platform $name
+	    eval $body
+	    set ::current_platform ""
+	}
+	
+	proc startup { types } {
+	    set_target_data "$::current_target,$::current_platform,startup" $types
+	}
+
+	# The cflags command is much more complicated than the others. The argument
+	# to cflags should be a list of paired data, e.g. ERRFLAGS -Wall.
+	# The first entry in each pair should be one of the known compiler
+	# flags, the second one should be its value.
+	proc cflags { str } {
+
+	    set arg_count [llength $str]
+	    if { ($arg_count % 2) != 0 } {
+		error "Invalid argument list to cflags command in $current_target"
+	    }
+	    for { set i 0 } { $i < $arg_count } { incr i 2 } {
+		set flag [lindex $str $i]
+		set value [lindex $str [expr $i + 1]]
+		if { [lsearch -exact $::known_compiler_flags $flag] == -1 } {
+		    error "Invalid compiler flag $flag for target $::current_target"
+		}
+		if { $::current_platform != "" } {
+		    set_target_data "$::current_target,$::current_platform,cflags,$flag" $value
+		} else {
+		    set_target_data "$::current_target,cflags,$flag" $value
+		}
+	    }
+	}
+	
+    }
+    
+    # The parser is ready to evaluate the script. To avoid having to give the
+    # safe interpreter file I/O capabilities, the file is actually read in
+    # here and then evaluated.
+    set filename [file join $pkgconf::component_repository "targets"]
+    set status [ catch {
+	set fd [open $filename r]
+	set script [read $fd]
+	close $fd
+	$parser eval $script
+    } message]
+
+    if { $status != 0 } {
+	puts "Unexpected error while processing $filename\n$message"
+	exit 1
+    }
+
+    # The interpreter and the aliased commands are no longer required.
+    rename add_known_target   {}
+    rename add_known_platform {}
+    rename set_target_data    {}
+    interp delete $parser
+
+    # The targets file has been read in, so it is time for some consistency checks.
+    foreach target $pkgconf::known_targets {
+
+	# If this target involves some special packages, check that these
+	# packages were actually defined in the packages file. Also check
+	# that these packages are listed as hardware packages so that
+	# they will not be enabled by default.
+	ASSERT { [info exists pkgconf::target_data($target,packages)] }
+	foreach pkg $pkgconf::target_data($target,packages) {
+
+	    if { [is_package $pkg] == 0 } {
+		fatal_error \
+"The targets file $filename lists a package $pkg\
+for the $target architecture. This package is not listed\
+in the packages file."
+
+            } elseif { [is_hardware_package $pkg] == 0 } {
+
+	        fatal_error \
+"The targets file $filename lists a package $pkg\
+for the $target architecture. According to the packages\
+file this package is not hardware-specific."
+	    }
+	}
+	
+	ASSERT { [info exists pkgconf::target_data($target,known_platforms)] }
+	foreach platform $pkgconf::target_data($target,known_platforms) {
+
+	    ASSERT { [info exists pkgconf::target_data($target,$platform,packages)] }
+	    foreach pkg $pkgconf::target_data($target,packages) {
+
+		if { [is_package $pkg] == 0 } {
+		    
+		    fatal_error \
+"The target file $filename lists a package $pkg\
+for the $target architecture and the $platform platform.\
+This package is not listed in the packages file."
+
+                } elseif { [is_hardware_package $pkg] == 0 } {
+
+			fatal_error \
+"The target file $filename lists a package $pkg\
+for the $target architecture and the $platform platform.\
+According to the packages file this package is not hardware-specific."
+		}
+	    }
+	}
+
+	# Also check that all required data for all targets and platforms has
+	# been supplied. A command prefix is required, as is a startup list.
+
+	if { [llength $pkgconf::target_data($target,known_platforms)] == 0 } {
+	    warning "The targets file $filename lists the $target architecture but no platforms."
+	    set index [lsearch -exact $pkgconf::known_targets $target]
+	    ASSERT { $index != -1 }
+	    set pkgconf::known_targets [lreplace $pkgconf::known_targets $index $index]
+	    continue
+	}
+
+	if { $pkgconf::target_data($target,prefix) == "" } {
+	    fatal_error "The targets file $filename does not list a command prefix for the $target architecture."
+	}
+
+	foreach platform $pkgconf::target_data($target,known_platforms) {
+	    if { [llength $pkgconf::target_data($target,$platform,startup)] == 0 } {
+		fatal_error \
+"The targets file $filename does not list any startup options for target $target, platform $platform."
+	    }
+	}
+    }
+
+    # At this stage there may be no targets left over.
+    if { [llength $pkgconf::known_targets] == 0 } {
+	fatal_error "The targets file $filename does not list any usable targets."
+    }
+    
+    # All the directories exist. Did the user ask for a list of all targets ?
+    if { $pkgconf::list_targets_arg } {
+
+	# The user wants to know about all the targets. Just to be fancy
+	# this code does some alignment.
+	set name_maxlen 0
+	
+	foreach target $pkgconf::known_targets {
+	    if { [string length $target] > $name_maxlen} {
+		set name_maxlen [string length $target]
+	    }
+	}
+	
+	puts "Known targets:"
+	foreach target $pkgconf::known_targets {
+	    
+	    puts "[format "  %-*s : aliases $pkgconf::target_data($target,alias)" $name_maxlen $target]"
+	    
+	    set packages $pkgconf::target_data($target,packages)
+	    if { [llength $packages] > 0 } {
+		puts "    Required packages"
+		foreach package $packages {
+		    puts "        [pkgconf::get_package_alias $package] ($package)"
+		}
+	    }
+	    
+	    foreach platform $pkgconf::target_data($target,known_platforms) {
+		puts "    Platform $platform, supported startups $pkgconf::target_data($target,$platform,startup)"
+		set packages $pkgconf::target_data($target,$platform,packages)
+		if { [llength $packages] > 0 } {
+		    puts "      Required packages"
+		    foreach package $packages {
+			puts "          [pkgconf::get_package_alias $package] ($package)"
+		    }
+		}
+	    }
+	}
+    }
+}
+
+#
+# Given a target name as supplied by the user, return the internal target name.
+# This involves searching through the list of aliases.
+#
+proc pkgconf::find_target { name } {
+
+    foreach target $pkgconf::known_targets {
+
+	if { [string toupper $target] == [string toupper $name] } {
+	    return $target
+	}
+
+	foreach alias $pkgconf::target_data($target,alias) {
+	    if { [string toupper $alias] == [string toupper $name] } {
+		return $target
+	    }
+	}
+    }
+
+    return ""
+}
+
+#
+# Ditto but for a given platform name. The target name must be known already.
+#
+
+proc pkgconf::find_platform { target name } {
+
+    pkgconf::ASSERT { [lsearch -exact $pkgconf::known_targets $target] != -1 }
+
+    foreach platform $pkgconf::target_data($target,known_platforms) {
+
+	if { [string toupper $platform] == [string toupper $name] } {
+	    return $platform
+	}
+
+	foreach alias $pkgconf::target_data($target,$platform,alias) {
+
+	    if { [string toupper $alias] == [string toupper $name] } {
+		return $platform
+	    }
+	}
+    }
+    return ""
+}
+
+#
+# Given a target name, return the first alias. This is useful for output
+# code.
+#
+proc pkgconf::get_target_alias { name } {
+
+    pkgconf::ASSERT { [lsearch -exact $pkgconf::known_targets $name] != -1 }
+
+    set alias [lindex $pkgconf::target_data($name,alias) 0]
+    return $alias
+}
+
+#
+# Ditto for platforms.
+#
+proc pkgconf::get_platform_alias { target name } {
+
+    pkgconf::ASSERT { [lsearch -exact $pkgconf::known_targets $target] != -1 }
+    pkgconf::ASSERT { [lsearch -exact $pkgconf::target_data($target,known_platforms) $name] != -1 }
+
+    set alias [lindex $pkgconf::target_data($target,$name,alias) 0]
+    return $alias
+}
+
+#
+# For a given target and platform, return an appropriate set of compiler flags.
+#
+proc pkgconf::get_platform_cflags { target platform flag } {
+
+    ASSERT { [lsearch -exact $pkgconf::known_targets $target] != -1 }
+    ASSERT { [lsearch -exact $pkgconf::target_data($target,known_platforms) $platform] != -1 }
+    ASSERT { [lsearch -exact $pkgconf::known_compiler_flags $flag] != -1 }
+
+    # A particular flag may be defined specifically for a platform.
+    # Or it may come from the target information. Or there may not be
+    # a default value.
+    if { [info exists pkgconf::target_data($target,$platform,cflags,$flag)] != 0 } {
+	return $pkgconf::target_data($target,$platform,cflags,$flag)
+    } elseif { [info exists pkgconf::target_data($target,cflags,$flag)] != 0 } {
+	return $pkgconf::target_data($target,cflags,$flag)
+    } else {
+	return ""
+    }
+}
+
+#
+# Is a given target or platform actually valid ? This code copes
+# with aliases as well.
+proc pkgconf::is_target { name } {
+
+    ASSERT { $name != "" }
+
+    set name [find_target $name]
+    set result 0
+    if { $name != "" } {
+	set result 1
+    }
+    return $result
+}
+
+proc pkgconf::is_platform { target name } {
+
+    ASSERT { $target != "" }
+    ASSERT { $name   != "" }
+
+    set target [find_target $target]
+    ASSERT { $target != "" }
+    
+    set name [find_platform $target $name]
+    set result 0
+    if { $name != "" } {
+	set result 1
+    }
+    return $result
+}
+
+proc pkgconf::is_startup { target platform name } {
+
+    ASSERT { $target != "" }
+    ASSERT { $name   != "" }
+
+    set target [find_target $target]
+    ASSERT { $target != "" }
+
+    set platform [find_platform $target $platform]
+    ASSERT { $platform != "" }
+
+    set index [lsearch -exact $pkgconf::target_data($target,$platform,startup) $name]
+    set result 0
+    if { $index != -1 } {
+	set result 1
+    }
+    return $result
+}
+
+#
+# Get hold of the default platform and startup for a given target.
+
+proc pkgconf::get_default_platform { target } {
+
+    ASSERT { $target != "" }
+    
+    set target [find_target $target]
+    ASSERT { $target != "" }
+
+    set platform [lindex $pkgconf::target_data($target,known_platforms) 0]
+    return $platform
+}
+
+proc pkgconf::get_default_startup { target platform } {
+
+    ASSERT { $target   != "" }
+    ASSERT { $platform != "" }
+
+    set target [find_target $target]
+    ASSERT { $target != "" }
+    set platform [find_platform $target $platform]
+    ASSERT { $platform != "" }
+    
+    set startup [lindex $pkgconf::target_data($target,$platform,startup) 0]
+    return $startup
+}
+
+# }}}
+# {{{  Configuration Data               
+
+# {{{  setup_defaults                   
+
+# ----------------------------------------------------------------------------
+# The setup_defaults routine is responsible for deciding on default values.
+# If there is only one supported architecture then this can be selected,
+# and then it is also possible to select a platform and a startup mode.
+# All known packages can be enabled by default, and the latest version
+# of each package can be selected.
+#
+# All configuration data is held in the array config_data(). It is
+# important to make sure that all fields are initialised.
+#
+
+proc pkgconf::setup_defaults { } {
+
+    ASSERT { [llength [array names pkgconf::config_data] ] == 0 }
+    ASSERT { $pkgconf::known_packages != "" }
+    ASSERT { $pkgconf::known_targets  != "" }
+    ASSERT { $pkgconf::build_tree != "" }
+    
+    set pkgconf::config_data(target)    ""
+    set pkgconf::config_data(platform)  ""
+    set pkgconf::config_data(startup)   ""
+    set pkgconf::config_data(prefix)    ""
+    set pkgconf::config_data(use_links) 0
+
+    # Packages fall into a number of different categories.
+    #
+    # 1) some packages are hardware-specific. They should be enabled
+    #    by default if that hardware has been selected, but the user
+    #    must still be able to disable them in case they cause problems.
+    #
+    # 2) some packages are enabled by default, but can be disabled by
+    #    the user. There are actually some packages that should never
+    #    be disabled, especially infra, but that would complicate things.
+    #
+    # 3) some packages are disabled by default, but can be enabled by
+    #    the user.
+    #
+    # The easiest way to track this information is to store the target
+    # and platform data, which gives us the hardware packages, plus
+    # separate lists of which packages are explicitly enabled or disabled.
+    # All other information can be derived from that.
+    set pkgconf::config_data(enabled_packages)  ""
+    set pkgconf::config_data(disabled_packages) ""
+
+    # For each package fill in a default version number.
+    foreach pkg $pkgconf::known_packages {
+	
+	ASSERT { $pkgconf::package_data($pkg,versions) != "" }
+	ASSERT { [info exists pkgconf::config_data($pkg,version)] == 0 }
+	set pkgconf::config_data($pkg,version) [lindex $pkgconf::package_data($pkg,versions) 0]
+    }
+
+    # If there is only one target architecture, default to it. Also
+    # default to the first platform for that target and the first
+    # startup for that platform.
+    if { [llength $pkgconf::known_targets] == 1 } {
+ 	set pkgconf::config_data(target) [lindex $pkgconf::known_targets 0]
+
+	ASSERT { $pkgconf::target_data($pkgconf::config_data(target),known_platforms) != "" }
+	set pkgconf::config_data(platform) [get_default_platform $pkgconf::config_data(target)]
+
+	ASSERT { $pkgconf::target_data($pkgconf::config_data(target),$pkgconf::config_data(platform),startup) != "" }
+	set pkgconf::config_data(startup) [get_default_startup $pkgconf::config_data(target) $pkgconf::config_data(platform)]
+    }
+
+    if { [info exists ::env(PKGCONF_USE_LINKS)] } {
+	set pkgconf::config_data(use_links) 1
+    }
+
+    if { 0 } {
+	foreach i [array names pkgconf::config_data] {
+	    puts "pkgconf::config_data($i) : $pkgconf::config_data($i)"
+	}
+	exit 0
+    }
+}
+
+# }}}
+# {{{  read_save_file                   
+
+# ----------------------------------------------------------------------------
+# The build tree may have a save file. The aim here is that
+# if the user wants to change some aspects of the configuration, e.g. to
+# switch between the eval board and the simulator, it should not be
+# necessary to pass all the command line arguments again. Instead the
+# system should work out past settings and re-use those where possible.
+#
+# This routine should be invoked after the defaults are set up but
+# before the command line arguments take effect.
+#
+# There is a command line argument --defaults which stops the saved
+# values from taking effect. The save file is still read in because
+# it is useful to know what is in the build tree at the moment.
+#
+
+proc pkgconf::read_save_file { } {
+
+    ASSERT { $pkgconf::build_tree != "" }
+    
+    # Get rid of any old settings that might be hanging around.
+    foreach i [array names pkgconf::saved_data] {
+	unset pkgconf::saved_data($i)
+    }
+
+    set filename [file join $pkgconf::build_tree "pkgconf.sav"]
+    if { [file exists $filename] && [file isfile $filename] } {
+	
+	# Read in the saved data. A safe interpreter is used, just in case.
+	proc set_saved_data { name value } {
+	    set pkgconf::saved_data($name) $value
+	}
+	set parser [interp create -safe]
+	$parser alias set_saved_data pkgconf::set_saved_data
+
+	set status [ catch {
+	    set fd [open $filename r]
+	    set script [read $fd]
+	    close $fd
+	    $parser eval $script
+	} message]
+	if { $status != 0 } {
+	    pkgconf::fatal_error "Unexpected error while reading back save file $filename\n$message"
+	}
+
+	rename set_saved_data {}
+	interp delete $parser
+    }
+
+    # It is a good idea to check that the data read back is consistent.
+    # It is also a good idea to check some aliases, in case something
+    # has been renamed between releases.
+    if { [info exists pkgconf::saved_data(target)] } {
+
+	set target_name [find_target $pkgconf::saved_data(target)]
+	if { $target_name == "" } {
+	    warning "The save file $filename contained an invalid target $pkgconf::saved_data(target). This has been ignored."
+	} else {
+	    set pkgconf::saved_data(target)    $target_name
+	    set pkgconf::config_data(target)   $target_name
+	    set pkgconf::config_data(platform) [get_default_platform $pkgconf::config_data(target)]
+	    set pkgconf::config_data(startup) \
+		    [get_default_startup $pkgconf::config_data(target) $pkgconf::config_data(platform)]
+	}
+    }
+
+    # There is no point in validating platform or startup unless the target is known.
+    if { $pkgconf::config_data(target) != "" } {
+
+	ASSERT { $pkgconf::target_data($pkgconf::config_data(target),known_platforms) != "" }
+	
+	if { [info exists pkgconf::saved_data(platform)] } {
+
+	    set platform_name [find_platform $pkgconf::config_data(target) $pkgconf::saved_data(platform)]
+	    if { $platform_name == "" } {
+		warning \
+"The save file $filename specified an unknown platform $pkgconf::saved_data(platform).\
+The platform $pkgconf::config_data(platform) has been substituted."
+	    } else {
+		set pkgconf::saved_data(platform)  $platform_name
+		set pkgconf::config_data(platform) $platform_name
+		set pkgconf::config_data(startup) [get_default_startup $pkgconf::config_data(target) $platform_name]
+	    }
+	    
+	}
+
+	# Currently if there is a target then there is always a platform,
+	# possibly the default one.
+	ASSERT { $pkgconf::target_data($pkgconf::config_data(target),$pkgconf::config_data(platform),startup) != "" }
+
+	if { [info exists pkgconf::saved_data(startup)] } {
+
+	    if { [is_startup $pkgconf::config_data(target) $pkgconf::config_data(platform) $pkgconf::saved_data(startup)] \
+		    == 0 } {
+		warning \
+"The save file $filename specified an unknown startup $pkgconf::saved_data(startup).\
+$pkgconf::config_data(startup) startup has been substituted."
+	    } else {
+		set pkgconf::config_data(startup) $pkgconf::saved_data(startup)
+	    }
+	}
+    }
+
+    # There should be an entry listing all packages for which there is
+    # a version entry. This can be used to update the version numbers
+    # which should be used in this configuration.
+    if { [info exists pkgconf::saved_data(versioned_packages) ] != 0 } {
+	foreach pkg $pkgconf::saved_data(versioned_packages) {
+	    
+	    # Check that there is a saved version string for this package.
+	    # There should be, but it is not worth worrying about.
+	    if { [info exists pkgconf::saved_data($pkg,version)] == 0 } {
+		continue
+	    }
+	    set version $pkgconf::saved_data($pkg,version)
+
+	    # Does this package still exist ?
+	    if { [is_package $pkg] == 0 } {
+		warning \
+"The save file $filename specified version $version for package $pkg.\
+This package does not exist in the current repository, so the version string\
+has been ignored."
+                continue
+	    }
+	    set pkg [find_package $pkg]
+	    ASSERT { $pkg != "" }
+
+	    # Is the version valid ?
+	    if { [is_valid_version $pkg $version] == 0 } {
+		warning \
+"The save file $filename specified version $version for package $pkg.\
+This version is not present in the current repository so the version\
+string has been ignored."
+	    } else {
+		set pkgconf::config_data($pkg,version) $version
+	    }
+	}
+    }
+
+    # Now check that all the packages listed in the save file are known.
+    # If any are missing then they are just discarded. Known packages
+    # are used to update the appropriate current configuration data.
+    if { [info exists pkgconf::saved_data(enabled_packages)] == 0 } {
+	set pkgconf::saved_data(enabled_packages) ""
+    }
+    if { [info exists pkgconf::saved_data(disabled_packages)] == 0 } {
+	set pkgconf::saved_data(disabled_packages) ""
+    }
+
+    foreach pkg $pkgconf::saved_data(enabled_packages) {
+
+	if { [is_package $pkg] == 0 } {
+	    warning \
+"The save file $filename mentioned a package $pkg.\
+This package does not exist in the current repository and
+has been ignored."
+            continue
+	}
+
+	set pkg   [find_package $pkg]
+	set index [lsearch -exact $pkgconf::config_data(enabled_packages) $pkg]
+	if { $index == -1 } {
+	    lappend pkgconf::config_data(enabled_packages) $pkg
+	}
+    }
+
+    foreach pkg $pkgconf::saved_data(disabled_packages) {
+
+	if { [is_package $pkg] == 0 } {
+	    warning \
+"The save file $filename mentioned a package $pkg.\
+This package does not exist in the current repository and
+has been ignored."
+            continue
+	}
+
+	set pkg [find_package $pkg]
+	set index [lsearch -exact $pkgconf::config_data(disabled_packages) $pkg]
+	if { $index == -1 } {
+	    lappend pkgconf::config_data(disabled_packages) $pkg
+	}
+
+	# Better make sure that a package is not simultaneously enabled and disabled.
+	set index [lsearch -exact $pkgconf::config_data(enabled_packages) $pkg]
+	if { $index != -1 } {
+	    set pkgconf::config_data(enabled_packages) [lreplace $pkgconf::config_data(enabled_packages) $index $index]
+	}
+    }
+
+    # Now check for any compiler flags
+    foreach flag $pkgconf::known_compiler_flags {
+	if { [info exists pkgconf::saved_data(cflags,$flag)] } {
+	    set pkgconf::config_data(cflags,$flag) $pkgconf::saved_data(cflags,$flag)
+	}
+    }
+    
+    # Update the install directory if appropriate.
+    # There is not a lot of checking that can be done here.
+    if { [info exists pkgconf::saved_data(prefix) ] } { 
+	set pkgconf::config_data(prefix) $pkgconf::saved_data(prefix)
+    }
+
+    # And check for the use_links option
+    if { [info exists pkgconf::saved_data(use_links)] } {
+	set pkgconf::config_data(use_links) $pkgconf::saved_data(use_links)
+    }
+    
+    # Now, possibly undo all of this if the user specified --defaults.
+    if { $pkgconf::defaults_arg != 0 } {
+	foreach entry [array names pkgconf::config_data] {
+	    unset pkgconf::config_data($entry)
+	}
+	setup_defaults
+    }
+    
+    if { 0 } {
+	foreach i [array names pkgconf::config_data] {
+	    puts "pkgconf::config_data($i) : $pkgconf::config_data($i)"
+	}
+	exit 0
+    }
+}
+
+# }}}
+# {{{  write_save_file                  
+
+# ----------------------------------------------------------------------------
+# Outputting the save file. This is a relatively simple operation assuming
+# no file I/O errors happen. The saved_data array is updated with the new
+# contents of the save file. It is not yet clear whether this will be useful,
+# but it should not cause any problems either.
+#
+
+proc pkgconf::write_save_file { } {
+
+    # Check for minimal consistency. If any variables are missing then
+    # this will be handled (poorly) by the catch statement.
+    ASSERT { [info exists pkgconf::config_data(target)] }
+    ASSERT { $pkgconf::build_tree != "" }
+
+    set savename [file join $pkgconf::build_tree "pkgconf.sav"]
+    set open_file ""
+    
+    set status [catch {
+
+	set open_file [open $savename w]
+	output_banner $open_file "pkgconf.sav"
+
+        if { $pkgconf::config_data(target) != "" } {
+            puts $open_file "set_saved_data target   $pkgconf::config_data(target)"
+	    set pkgconf::saved_data(target) $pkgconf::config_data(target)
+	}
+	if { $pkgconf::config_data(platform) != "" } {
+	    puts $open_file "set_saved_data platform $pkgconf::config_data(platform)"
+	    set pkgconf::saved_data(platform) $pkgconf::config_data(platform)
+	}
+	if { $pkgconf::config_data(startup) != "" } {
+	    puts $open_file "set_saved_data startup  $pkgconf::config_data(startup)"
+	    set pkgconf::saved_data(startup) $pkgconf::config_data(startup)
+	}
+	if { $pkgconf::config_data(enabled_packages) != "" } {
+	    # The use of the list command here avoids problems with multiple
+	    # arguments when the file is read back.
+	    puts $open_file "set_saved_data enabled_packages [list $pkgconf::config_data(enabled_packages)]"
+	    set pkgconf::saved_data(enabled_packages) $pkgconf::config_data(enabled_packages)
+	}
+	if { $pkgconf::config_data(disabled_packages) != "" } {
+	    puts $open_file "set_saved_data disabled_packages [list $pkgconf::config_data(disabled_packages)]"
+	    set pkgconf::saved_data(disabled_packages) $pkgconf::config_data(disabled_packages)
+	}
+        foreach pkg $pkgconf::known_packages {
+	    ASSERT { [info exists pkgconf::config_data($pkg,version)] }
+	    puts $open_file "set_saved_data \"$pkg,version\" $pkgconf::config_data($pkg,version)"
+	    set pkgconf::saved_data($pkg,version) $pkgconf::config_data($pkg,version)
+	}
+	# This allows the versions to be read back in more easily, even if
+	# there is a change to the number of packages.
+	puts $open_file "set_saved_data versioned_packages [list $pkgconf::known_packages]"
+	
+	foreach flag $pkgconf::known_compiler_flags {
+	    if { [info exists pkgconf::config_data(cflags,$flag)] != 0 } {
+		puts $open_file "set_saved_data \"cflags,$flag\" \"$pkgconf::config_data(cflags,$flag)\""
+	    }
+	}
+	if { $pkgconf::config_data(use_links) != 0 } {
+	    puts $open_file "set_saved_data use_links 1"
+	}
+
+	puts $open_file "set_saved_data prefix   \"$pkgconf::config_data(prefix)\""
+	set pkgconf::saved_data(prefix) $pkgconf::config_data(prefix)
+	
+	puts  $open_file "\n# End of $savename"
+	close $open_file
+    } message ]
+
+    if { $status != 0 } {
+
+	if { $open_file != "" } {
+	    # Whatever error occurred, it happened after the file was opened.
+	    # This is bad news, the file may be corrupt. To avoid complications,
+	    # try to delete it.
+	    catch {
+		close $open_file
+		file delete $savename
+	    }
+	    # Also get rid of the saved_data array.
+	    foreach i [array names saved_data] {
+		unset saved_data($i)
+	    }
+	}
+	fatal_error "An unexpected error occurred while generating the save file $savename: $message"
+    }
+}
+
+# }}}
+# {{{  process_arguments()              
+
+# ----------------------------------------------------------------------------
+# This procedure takes care of updating the configuration information using
+# the command line arguments, for example if there was an argument specifying
+# a different version for a particular package then it is this procedure
+# which takes care of it. This procedure also takes care of validating the
+# arguments.
+#
+# This procedure should be invoked after setup_defaults has filled in the
+# default values for all configuration data, and after read_save_file
+# has updated them with saved data.
+#
+# There is a separate validation routine which gets invoked first.
+# This takes care of serious errors in the command line arguments.
+#
+
+proc pkgconf::process_arguments { } {
+
+    variable known_compiler_flags
+    variable known_packages
+    variable package_data
+    variable known_targets
+    variable target_data
+    
+    variable config_data
+    variable target_arg
+    variable platform_arg
+    variable startup_arg
+    variable prefix_arg
+    variable disable_args
+    variable enable_args
+    variable version_args
+    variable use_links_arg
+    variable use_copies_arg
+    variable cflags_args
+
+    # Constructing the file name strings here facilitates generating
+    # error messages.
+    set packages_file [file join $pkgconf::component_repository "packages"]
+    set targets_file  [file join $pkgconf::component_repository "targets"]
+
+    # The target, platform, and startup values are used throughout so it
+    # is convenient to have locals.
+    set target   $pkgconf::config_data(target)
+    set platform $pkgconf::config_data(platform)
+    set startup  $pkgconf::config_data(startup)
+    
+    # Start by validating the --target argument This target must
+    # be present in the known_targets list. If it is then the
+    # target architecture can be updated.
+    
+    if { $pkgconf::target_arg != "" } {
+
+	set target [find_target $pkgconf::target_arg]
+	if { $target == "" } {
+	    fatal_error "Invalid argument --target=$target_arg, this target is not listed in $targets_file"
+	}
+	set pkgconf::config_data(target) $target
+
+	# It is possible that the platform and startup data from the save file
+	# are no longer valid because of a change in the target. If so then the
+	# relevant fields have to be cleared.
+	if { $platform != "" } {
+	    set platform [find_platform $target $platform]
+	    set pkgconf::config_data(platform) $platform
+	    if { ($platform != "") && ($startup != "") } {
+		if { [lsearch -exact $pkgconf::target_data($target,$platform,startup) $startup] == -1 } {
+		    set startup ""
+		    set pkgconf::config_data(startup) ""
+		}
+	    }
+	}
+	
+	# Next, if the platform and startup are not currently known then
+	# it is possible to set up defaults for this target.
+	if { $platform == "" } {
+	    set platform [lindex $pkgconf::target_data($target,known_platforms) 0]
+	    set pkgconf::config_data(platform) $platform
+	} 
+	if { $startup == "" } {
+	    set startup [lindex $pkgconf::target_data($target,$platform,startup) 0]
+	    set pkgconf::config_data(startup) $startup
+	}
+    }
+
+    # If the target is known then it is possible to verify platform and startup
+    # arguments. Without a target these arguments are meaningless.
+    
+    if { ($target != "") && ($pkgconf::platform_arg != "") } {
+
+	set platform [find_platform $target $pkgconf::platform_arg]
+	if { $platform == "" } {
+	    fatal_error "Invalid argument --platform=$pkgconf::platform_arg, this platform is not listed in $targets_file"
+	} 
+	set pkgconf::config_data(platform) $platform
+    }
+
+    if { ($target != "") && ($platform != "") && ($pkgconf::startup_arg != "" ) } {
+	
+	if { [lsearch -exact $pkgconf::target_data($target,$platform,startup) $pkgconf::startup_arg] == -1 } {
+	    fatal_error "Invalid argument --startup=$startup_arg, this startup is not listed in $targets_file"
+	}
+	set startup $pkgconf::startup_arg
+	set config_data(startup) $startup
+    }
+
+    # It is not really possible to validate the prefix argument.
+    if { $pkgconf::prefix_arg != "" } {
+	set pkgconf::config_data(prefix) $pkgconf::prefix_arg
+    }
+
+    # Cope with the use of symbolic links vs. copies
+    if { $pkgconf::use_links_arg } {
+	set pkgconf::config_data(use_links) 1
+    }
+    if { $pkgconf::use_copies_arg } {
+	set pkgconf::config_data(use_links) 0
+    }
+    
+    # For each package that is being disabled, make sure it is on the
+    # list of disabled packages and not on the list of enabled packages.
+    # The former operation is actually unnecessary if the package is
+    # disabled by default, but harmless.
+    foreach pkg $pkgconf::disable_args {
+	set match [find_package $pkg]
+	if { $match == "" } {
+	    fatal_error "Invalid argument --disable-$pkg, package $pkg is not listed in $packages_file."
+	}
+
+	set index [lsearch -exact $pkgconf::config_data(enabled_packages) $match]
+	if { $index != -1 } {
+	    set pkgconf::config_data(enabled_packages) [lreplace $pkgconf::config_data(enabled_packages) $index $index]
+	}
+	set index [lsearch -exact $pkgconf::config_data(disabled_packages) $match]
+	if { $index == -1 } {
+	    lappend pkgconf::config_data(disabled_packages) $match
+	}
+    }
+
+    # For each package that should be enabled, add it to the list of enabled
+    # packages and remove it from the list of disabled packages. Again the
+    # former is actually unnecessary if the package is enabled by default.
+    foreach pkg $pkgconf::enable_args {
+	set match [find_package $pkg]
+	if { $match == "" } {
+	    fatal_error "Invalid argument --enable-$pkg, package $pkg is not listed in $packages_file."
+	}
+
+	set index [lsearch -exact $pkgconf::config_data(disabled_packages) $match]
+	if { $index != -1 } {
+	    set pkgconf::config_data(disabled_packages) [lreplace $pkgconf::config_data(disabled_packages) $index $index]
+	}
+	set index [lsearch -exact $pkgconf::config_data(enabled_packages) $match]
+	if { $index == -1 } {
+	    lappend config_data(enabled_packages) $match
+	}
+    }
+    
+    # For each package where a version has been specified, validate this
+    # version and use it. Also make sure that the package is enabled.
+    foreach pkg [array names pkgconf::version_args] {
+
+	set match [find_package $pkg]
+	if { $match == "" } {
+	    fatal_error "Invalid argument --$pkg-version, package $pkg is not listed in $packages_file."
+	}
+	if { [lsearch -exact $pkgconf::package_data($match,versions) $pkgconf::version_args($pkg)]  == -1 } {
+	    fatal_error "Invalid argument --$pkg-version=$pkgconf::version_args($pkg), that version is not available."
+	}
+	set pkgconf::config_data($match,version) $pkgconf::version_args($pkg)
+	
+	set index [lsearch -exact $pkgconf::config_data(disabled_packages) $match]
+	if { $index != -1 } {
+	    set pkgconf::config_data(disabled_packages) [lreplace $pkgconf::config_data(disabled_packages) $index $index]
+	}
+	if { ($pkgconf::package_data($match,hardware) == 1) || ($pkgconf::package_data($match,default) == 0) } {
+	    set index [lsearch -exact $pkgconf::config_data(enabled_packages) $match]
+	    if { $index == -1 } {
+		lappend config_data(enabled_packages) $match
+	    }
+	}
+    }
+
+    # For each compiler flag that has been specified explicitly on the command line,
+    # store it in the configuration data
+    foreach flag [array names pkgconf::cflags_args] {
+	set pkgconf::config_data(cflags,$flag) $pkgconf::cflags_args($flag)
+    }
+}
+
+# }}}
+# {{{  reset_data()                     
+
+# ----------------------------------------------------------------------------
+# This routine gets rid of all data. It can be used if the script is
+# switching from one configuration to a different one.
+#
+proc pkgconf::reset_data { } {
+
+    foreach i [array names pkgconf::config_data] {
+	unset pkgconf::config_data($i)
+    }
+    foreach i [array names pkgconf::saved_data] {
+	unset pkgconf::saved_data($i)
+    }
+}
+
+# }}}
+
+# }}}
+# {{{  GUI                              
+
+# {{{  GUI variables                    
+
+namespace eval pkgconf {
+
+    namespace export gui
+
+    # Which editor should be used for fine-grained configuration options ?
+    # The VISUAL environment variable is the correct way to obtain this
+    # information on Unix operating systems. Windows machines do not
+    # seem to have an obvious equivalent.
+    variable editor ""
+    if { [info exists ::env(VISUAL)] } {
+	set editor $::env(VISUAL)
+    }
+
+    # The names of the pages in the notebook. These names are
+    # invented by the notebook widget.
+    variable target_frame   ""
+    variable packages_frame ""
+    variable options_frame  ""
+    variable flags_frame    ""
+    variable build_frame    ""
+
+    # It is convenient to keep track of whether or not certain information
+    # has changed. This simplifies the task of deciding whether or not
+    # a page in the notebook needs redrawing.
+    #
+    # Note: currently if the user makes a change and then undoes that
+    # change the variable remains set. It is possible to get around this,
+    # but that would require a bit more work.
+    variable packages_page_needs_refresh 0
+    variable options_page_needs_refresh  0
+    variable build_page_needs_refresh    0
+
+    # Is it currently possible to do a build, or is it necessary to
+    # update the build tree first.
+    variable build_tree_needs_update     0
+
+    # Each notebook page is given its own array of global data.
+    array set target_page_data {};
+    array set packages_page_data {};
+    array set options_page_data {};
+    array set flags_page_data {};
+    array set build_page_data {};
+    
+    # The state of the build output window. There are two flags to consider:
+    # whether the output window is visible or not; and whether the output
+    # window is docked or not.
+    variable output_docked  1
+    variable output_visible 0
+    
+    # For gui mode, what is the name of the toplevel window ?
+    # If this script is embedded in a larger application then it can
+    # be made to run inside a frame in that window.
+    namespace export set_toplevel_window
+    variable toplevel ".pkgconf"
+}
+
+# }}}
+# {{{  Effective Tcl support            
+
+# ----------------------------------------------------------------------------
+# For now the intention is to allow this script to be run from a completely
+# standard wish or tclsh interpreter, version 8.0 or later. This means it is
+# not possible to use extensions such as TIX or ITCL. However in the GUI
+# it is still desirable to have a notebook widget, so here is an
+# implementation in vanilla TK. The balloon widget from the same source
+# is used as well.
+#
+# The code derives from the book Effective Tcl/Tk programming by Mark
+# Harrison and Michael McLennan. The actual source code was obtained from
+# the Addison-Wesley web site and contains the following copyright.
+#
+#       This distribution contains examples from the "Effective Tcl/Tk
+#       Programming" book.  You can study these examples as you read
+#       through the book, and you can steal the code for your own
+#       applications!
+#
+#
+# The code is enclosed in a separate namespace. Also I have modified
+# the code in a number of places to make it more general-purpose.
+#
+namespace eval pkgconf::efftcl {
+
+# ----------------------------------------------------------------------
+#  EXAMPLE: tabnotebook that can dial up pages
+# ----------------------------------------------------------------------
+#  Effective Tcl/Tk Programming
+#    Mark Harrison, DSC Communications Corp.
+#    Michael McLennan, Bell Labs Innovations for Lucent Technologies
+#    Addison-Wesley Professional Computing Series
+# ======================================================================
+#  Copyright (c) 1996-1997  Lucent Technologies Inc. and Mark Harrison
+# ======================================================================
+
+# BLV fix: this script may execute in tclsh rather than in wish,
+# so the option command may not be available.    
+if { [info command "option"] == "option"} {
+
+    # BLV: I do not like this background colour for the tabs.
+    if { 0 } {
+	option add *Tabnotebook.tabs.background #666666 widgetDefault
+    }
+    option add *Tabnotebook.margin 6 widgetDefault
+    option add *Tabnotebook.tabColor #a6a6a6 widgetDefault
+    option add *Tabnotebook.activeTabColor #d9d9d9 widgetDefault
+    option add *Tabnotebook.tabFont \
+	    -*-helvetica-bold-r-normal--*-120-* widgetDefault
+}
+
+# BLV fix: replace global variables with per-namespace ones.
+array set tnInfo {}
+array set bhInfo {}
+
+proc tabnotebook_create {win} {
+    variable tnInfo
+
+    frame $win -class Tabnotebook
+    canvas $win.tabs -highlightthickness 0
+    pack $win.tabs -fill x
+
+    notebook_create $win.notebook
+    pack $win.notebook -expand yes -fill both
+
+    set tnInfo($win-tabs)    ""
+    set tnInfo($win-current) ""
+    set tnInfo($win-pending) ""
+    return $win
+}
+
+#
+# BLV fix: add three arguments, one specifying a function to be
+# invoked when the tab becomes visible, another for when the user
+# leaves the tab, and a text string for balloon help. This function can
+# be used to redraw a page if this is necessary due to changes in other
+# pages, and an empty string can be used if desired. An empty string
+# can also be used for the balloon help.
+#
+
+proc tabnotebook_page {win name enterfn leavefn help} {
+    variable tnInfo
+
+    set page [notebook_page $win.notebook $name]
+    lappend tnInfo($win-tabs)  $name
+    set tnInfo($name-enterfn)  $enterfn
+    set tnInfo($name-leavefn)  $leavefn
+    set tnInfo($name-help)     $help
+    
+    if {$tnInfo($win-pending) == ""} {
+        set id [after idle [list pkgconf::efftcl::tabnotebook_refresh $win]]
+        set tnInfo($win-pending) $id
+    }
+    return $page
+}
+
+proc tabnotebook_refresh {win} {
+    variable tnInfo
+
+    $win.tabs delete all
+
+    set margin [option get $win margin Margin]
+    set color [option get $win tabColor Color]
+    set font [option get $win tabFont Font]
+    set x 2
+    set maxh 0
+
+    foreach name $tnInfo($win-tabs) {
+        set id [$win.tabs create text \
+            [expr $x+$margin+2] [expr -0.5*$margin] \
+            -anchor sw -text $name -font $font \
+            -tags [list $name]]
+
+        set bbox [$win.tabs bbox $id]
+        set wd [expr [lindex $bbox 2]-[lindex $bbox 0]]
+        set ht [expr [lindex $bbox 3]-[lindex $bbox 1]]
+        if {$ht > $maxh} {
+            set maxh $ht
+        }
+
+        $win.tabs create polygon 0 0  $x 0 \
+            [expr $x+$margin] [expr -$ht-$margin] \
+            [expr $x+$margin+$wd] [expr -$ht-$margin] \
+            [expr $x+$wd+2*$margin] 0 \
+            2000 0  2000 10  0 10 \
+            -outline black -fill $color \
+            -tags [list $name tab tab-$name]
+
+        $win.tabs raise $id
+
+        $win.tabs bind $name <ButtonPress-1> \
+            [list pkgconf::efftcl::tabnotebook_display $win $name]
+
+	# BLV - add support for balloon help
+	if { $tnInfo($name-help) != "" } {
+	    balloonhelp_canvas_for $win.tabs $name $tnInfo($name-help)
+	}
+        set x [expr $x+$wd+2*$margin]
+    }
+    set height [expr $maxh+2*$margin]
+    $win.tabs move all 0 $height
+
+    $win.tabs configure -width $x -height [expr $height+4]
+
+    if {$tnInfo($win-current) != ""} {
+        tabnotebook_display $win $tnInfo($win-current)
+    } else {
+        tabnotebook_display $win [lindex $tnInfo($win-tabs) 0]
+    }
+    set tnInfo($win-pending) ""
+}
+
+proc tabnotebook_display {win name} {
+    variable tnInfo
+    variable nbInfo
+    
+    # BLV: invoke appropriate callbacks in the current and new
+    # tabs.
+    set old_tab $tnInfo($win-current)
+    if { ($old_tab != "") && ($tnInfo($old_tab-leavefn) != "") } {
+	 $tnInfo($old_tab-leavefn) $nbInfo($win.notebook-page-$old_tab))
+    }
+    if { $tnInfo($name-enterfn) != "" } {
+	$tnInfo($name-enterfn) $nbInfo($win.notebook-page-$name)
+    }
+    
+    notebook_display $win.notebook $name
+
+    set normal [option get $win tabColor Color]
+    $win.tabs itemconfigure tab -fill $normal
+
+    set active [option get $win activeTabColor Color]
+    $win.tabs itemconfigure tab-$name -fill $active
+    $win.tabs raise $name
+
+    set tnInfo($win-current) $name
+}
+
+# ----------------------------------------------------------------------
+#  EXAMPLE: simple notebook that can dial up pages
+# ----------------------------------------------------------------------
+#  Effective Tcl/Tk Programming
+#    Mark Harrison, DSC Communications Corp.
+#    Michael McLennan, Bell Labs Innovations for Lucent Technologies
+#    Addison-Wesley Professional Computing Series
+# ======================================================================
+#  Copyright (c) 1996-1997  Lucent Technologies Inc. and Mark Harrison
+# ======================================================================
+
+# BLV - again check for TK being present.
+if { [info command option] == "option" } {
+    option add *Notebook.borderWidth 2 widgetDefault
+    option add *Notebook.relief sunken widgetDefault
+}
+
+# BLV: replace nbInfo global with a per-namespace variable
+array set nbInfo {}
+
+proc notebook_create {win} {
+    variable nbInfo
+
+    frame $win -class Notebook
+    pack propagate $win 0
+
+    set nbInfo($win-count) 0
+    set nbInfo($win-pages) ""
+    set nbInfo($win-current) ""
+    return $win
+}
+
+proc notebook_page {win name} {
+    variable nbInfo
+
+    set page "$win.page[incr nbInfo($win-count)]"
+    lappend nbInfo($win-pages) $page
+    set nbInfo($win-page-$name) $page
+
+    frame $page
+
+    if {$nbInfo($win-count) == 1} {
+        after idle [list pkgconf::efftcl::notebook_display $win $name]
+    }
+    
+    return $page
+}
+
+proc notebook_display {win name} {
+    variable nbInfo
+
+    set page ""
+    if {[info exists nbInfo($win-page-$name)]} {
+        set page $nbInfo($win-page-$name)
+    } elseif {[winfo exists $win.page$name]} {
+        set page $win.page$name
+    }
+    if {$page == ""} {
+        error "bad notebook page \"$name\""
+    }
+
+    notebook_fix_size $win
+
+    if {$nbInfo($win-current) != ""} {
+        pack forget $nbInfo($win-current)
+    }
+    pack $page -expand yes -fill both
+    set nbInfo($win-current) $page
+}
+
+proc notebook_fix_size {win} {
+    variable nbInfo
+
+    update idletasks
+
+    set maxw 0
+    set maxh 0
+    foreach page $nbInfo($win-pages) {
+        set w [winfo reqwidth $page]
+        if {$w > $maxw} {
+            set maxw $w
+        }
+        set h [winfo reqheight $page]
+        if {$h > $maxh} {
+            set maxh $h
+        }
+    }
+    set bd [$win cget -borderwidth]
+    set maxw [expr $maxw+2*$bd]
+    set maxh [expr $maxh+2*$bd]
+    $win configure -width $maxw -height $maxh
+}
+
+# ----------------------------------------------------------------------
+#  EXAMPLE: use "wm" commands to manage a balloon help window
+# ----------------------------------------------------------------------
+#  Effective Tcl/Tk Programming
+#    Mark Harrison, DSC Communications Corp.
+#    Michael McLennan, Bell Labs Innovations for Lucent Technologies
+#    Addison-Wesley Professional Computing Series
+# ======================================================================
+#  Copyright (c) 1996-1997  Lucent Technologies Inc. and Mark Harrison
+# ======================================================================
+
+# BLV fix: again this script may execute in tclsh rather than in wish.
+if { [info command "option"] == "option" } {
+    option add *Balloonhelp*background white widgetDefault
+    option add *Balloonhelp*foreground black widgetDefault
+    option add *Balloonhelp.info.wrapLength 3i widgetDefault
+    option add *Balloonhelp.info.justify left widgetDefault
+    option add *Balloonhelp.info.font \
+	    -*-lucida-medium-r-normal-sans-*-120-* widgetDefault
+
+    toplevel .balloonhelp -class Balloonhelp \
+	    -background black -borderwidth 1 -relief flat
+
+    # BLV fix: I want to create the arrow image inline rather than
+    # read it in from a file.
+    image create bitmap arrow -data "\
+#define arrow_width 8
+#define arrow_height 8
+static unsigned char arrow_bits[] = {
+   0x1f, 0x0f, 0x0f, 0x1f, 0x39, 0x70, 0x20, 0x00};
+"
+    label .balloonhelp.arrow -anchor nw -image arrow
+
+#    label .balloonhelp.arrow -anchor nw \
+#	    -bitmap @[file join $env(EFFTCL_LIBRARY) images arrow.xbm]
+    pack .balloonhelp.arrow -side left -fill y
+
+    label .balloonhelp.info
+    pack .balloonhelp.info -side left -fill y
+
+    wm overrideredirect .balloonhelp 1
+    wm withdraw .balloonhelp
+}
+
+# ----------------------------------------------------------------------
+#  USAGE:  balloonhelp_for <win> <mesg>
+#
+#  Adds balloon help to the widget named <win>.  Whenever the mouse
+#  pointer enters this widget and rests within it for a short delay,
+#  a balloon help window will automatically appear showing the
+#  help <mesg>.
+# ----------------------------------------------------------------------
+
+proc balloonhelp_for {win mesg} {
+    variable bhInfo
+    set bhInfo($win) $mesg
+
+    bind $win <Enter> {pkgconf::efftcl::balloonhelp_pending %W %W}
+    bind $win <Leave> {pkgconf::efftcl::balloonhelp_cancel}
+}
+
+# BLV: a variant that can be used for canvas items.
+
+proc balloonhelp_canvas_for { canvas id mesg } {
+    variable bhInfo
+    set bhInfo($canvas-$id) $mesg
+
+    $canvas bind $id <Enter> [list pkgconf::efftcl::balloonhelp_pending $canvas $canvas-$id]
+    $canvas bind $id <Leave> {pkgconf::efftcl::balloonhelp_cancel}
+}
+
+# ----------------------------------------------------------------------
+#  USAGE:  balloonhelp_control <state>
+#
+#  Turns balloon help on/off for the entire application.
+# ----------------------------------------------------------------------
+set bhInfo(active) 1
+
+proc balloonhelp_control {state} {
+    variable bhInfo
+
+    if {$state} {
+        set bhInfo(active) 1
+    } else {
+        balloonhelp_cancel
+        set bhInfo(active) 0
+    }
+}
+
+# ----------------------------------------------------------------------
+#  USAGE:  balloonhelp_pending <win>
+#
+#  Used internally to mark the point in time when a widget is first
+#  touched.  Sets up an "after" event so that balloon help will appear
+#  if the mouse remains within the current window.
+# ----------------------------------------------------------------------
+
+# BLV - the first argument identifies the window, the second the help
+# string.
+proc balloonhelp_pending {win id} {
+    variable bhInfo
+
+    balloonhelp_cancel
+    set bhInfo(pending) [after 1500 [list pkgconf::efftcl::balloonhelp_show $win $id]]
+}
+
+# ----------------------------------------------------------------------
+#  USAGE:  balloonhelp_cancel
+#
+#  Used internally to mark the point in time when the mouse pointer
+#  leaves a widget.  Cancels any pending balloon help requested earlier
+#  and hides the balloon help window, in case it is visible.
+# ----------------------------------------------------------------------
+proc balloonhelp_cancel {} {
+    variable bhInfo
+
+    if {[info exists bhInfo(pending)]} {
+        after cancel $bhInfo(pending)
+        unset bhInfo(pending)
+    }
+    wm withdraw .balloonhelp
+}
+
+# ----------------------------------------------------------------------
+#  USAGE:  balloonhelp_show <win>
+#
+#  Used internally to display the balloon help window for the
+#  specified <win>.
+# ----------------------------------------------------------------------
+
+# BLV - the first argument identifies the window, the second the
+# help message.
+
+proc balloonhelp_show {win id} {
+    variable bhInfo
+
+    if {$bhInfo(active)} {
+        .balloonhelp.info configure -text $bhInfo($id)
+
+	# BLV: switched from using window-relative positions
+	# to mouse relative position.
+	if { 0 } {
+	    set x [expr [winfo rootx $win]+10]
+	    set y [expr [winfo rooty $win]+[winfo height $win]]
+	} else {
+	    set coords [winfo pointerxy $win]
+	    set x [expr [lindex $coords 0] + 15]
+	    set y [expr [lindex $coords 1] + 15]
+	}
+	if { ($x >= 0) && ($y >= 0) } {
+	    wm geometry .balloonhelp +$x+$y
+	    wm deiconify .balloonhelp
+	    raise .balloonhelp
+	}
+    }
+    unset bhInfo(pending)
+}
+
+}
+
+# }}}
+# {{{  standalone initialisation        
+
+# ----------------------------------------------------------------------------
+# GUI Start-up. It is necessary to work out a few things about the operating
+# environment.
+#
+# 1) is this script being run via tclsh or via wish ? If the former then only
+#    command line operation is possible. If the latter then a GUI environment
+#    can be provided as well.
+#
+# 2) if TK is present, has it been extended with Tix ? If so use the standard
+#    TK color scheme etc., not the Tix variant.
+#
+# 3) if TK is present, get rid of the top-level window.
+#
+# If this command is not being run stand-alone then it is the responsibility
+# of the calling code to take appropriate action, e.g. to enable gui mode
+# and to set up the appropriate options.
+#
+
+proc pkgconf::gui_standalone_init { } {
+
+    variable gui_possible
+    
+    if { [info command tk] == "tk" } {
+
+	# TK is loaded. For now allow GUI mode, but the actual decision
+	# depends on command line arguments.
+	set gui_possible 1
+
+	# Get rid of the top-level window. Otherwise this might pop up if the
+	# script performs a blocking operation too early.
+	wm withdraw .
+
+	# If TIX is also present, prevent the TIX color scheme etc. from taking
+	# effect.
+	if { [info command tix] == "tix" } {
+	    tix resetoptions TK TK
+	}
+	
+	# It is probably not a good idea to accept "send" messages. However
+	# setting the appname also affects option handling, which is more
+	# useful. Note that the send command is not available in all versions
+	# of Tcl.
+	if { [info command send] == "send" } {
+	    rename send {}
+	}
+	tk appname pkgconf
+    
+    } else {
+
+	# TK is not loaded, so gui mode is not possible.
+	set gui_possible 0
+    }
+}
+
+# }}}
+# {{{  set toplevel window              
+
+# ----------------------------------------------------------------------------
+# Allow a containing program, if any, to specify the window in which
+# this script should do its funky stuff. It is not possible to change
+# this window while pkgconf is active, so if for any reason it is
+# necessary to change the toplevel window then the old one must be
+# destroyed first, the new name must be installed, and then there
+# must be a new call to pkgconf::gui to redraw the window.
+#
+
+proc pkgconf::set_toplevel_window { name } {
+    variable toplevel
+    set toplevel $name
+}
+
+# }}}
+# {{{  main GUI draw routine            
+
+# ----------------------------------------------------------------------------
+# The main GUI code.
+#
+# The routine pkgconf::gui is responsible for creating the various windows.
+# These are as follows:
+#
+# 1) a menu bar containing menus for File, Window and Help. Currently the only
+#    entry in File is Exit, and the entries in Help are all disabled. The
+#    Window menu allows the output window to be hidden or shown.
+#
+# 2) a notebook frame containing four pages:
+#
+#    a) target hardware. This lists all known target architectures,
+#       and allows the user to select the appropriate one using a
+#       set of radio buttons.
+#
+#    b) packages. This lists all known packages and the versions for
+#       each package. Unfortunately neither radio buttons nor checkbuttons
+#       provide entirely the right paradigm here, but checkbuttons are
+#       good enough.
+#
+#    c) an edit page. This allows the user to invoke a text editor for
+#       any of the fine-grained configuration headers.
+#
+#    d) a compiler flags page. This allows the user to manipulate the
+#       compiler flags etc.
+#
+#    e) a build page. This allows the user to create the build tree and to
+#       invoke make, make tests, make clean, etc. There is also an entry
+#       field to allow the install directory to be changed.
+#
+# 3) a label field along the bottom, which can be used by the various
+#    frames to display warning messages.
+#
+# 4) a separate frame to hold the outputs of builds. This can be either
+#    docked below the main frame, or it can be a separate toplevel window.
+#    The build frame contains a number of buttons to allow the text
+#    screen to be cleared, removed from the display, docked/undocked,
+#    or saved to a file.
+#
+# 5) the options database is consulted to decide whether the output
+#    window should be docked or not by default.
+#
+# 6) if there is a file ~/.pkgconfrc this file gets executed. It is
+#    assumed to contain valid Tcl code.
+#
+# If this script is running inside a large application then there may be
+# a frame $toplevel_window already. If so this frame should be used.
+# Otherwise a separate toplevel window should be created, complete with
+# menu bar.
+#
+
+proc pkgconf::gui { } {
+
+    variable toplevel
+    if { [winfo exists $toplevel] == 0 } {
+	create_toplevel_window
+    }
+    
+    # Now create a notebook, and add four pages to it. The names of these
+    # pages have to be stored in separate variables.
+    variable target_frame
+    variable packages_frame
+    variable options_frame
+    variable flags_frame
+    variable build_frame
+    
+    efftcl::tabnotebook_create $toplevel.notebook
+    set target_frame   [efftcl::tabnotebook_page $toplevel.notebook "Target"   \
+	    pkgconf::nbpage_targets_enter pkgconf::nbpage_targets_leave        \
+	    "Select target hardware"]
+    set packages_frame [efftcl::tabnotebook_page $toplevel.notebook "Packages" \
+	    pkgconf::nbpage_packages_enter pkgconf::nbpage_packages_leave      \
+	    "Select desired packages"]
+    set options_frame  [efftcl::tabnotebook_page $toplevel.notebook "Options"  \
+	    pkgconf::nbpage_options_enter pkgconf::nbpage_options_leave        \
+	    "Edit configuration options"]
+    set flags_frame    [efftcl::tabnotebook_page $toplevel.notebook "Flags"    \
+	    pkgconf::nbpage_flags_enter pkgconf::nbpage_flags_leave            \
+	    "Edit compiler flags"]
+    set build_frame    [efftcl::tabnotebook_page $toplevel.notebook "Build"    \
+	    pkgconf::nbpage_build_enter pkgconf::nbpage_build_leave            \
+	    "Build this configuration"]
+
+    # By default the notebook is the widget that expands. If a build output
+    # window is present instead then it is the output window that expands.
+    pack $toplevel.notebook -side top -fill both -expand 1
+
+    # Make sure that the message window exists, since some of the
+    # notebook update routines refer to it.
+    label $toplevel.message -text "" -anchor w -relief raised -borderwidth 2
+    pack  $toplevel.message -side top -fill x
+
+    # Now create the notebook pages.
+    nbpage_targets_initialise  $target_frame
+    nbpage_packages_initialise $packages_frame
+    nbpage_options_initialise  $options_frame
+    nbpage_flags_initialise    $flags_frame
+    nbpage_build_initialise    $build_frame
+    
+    # Create the output window, but leave it hidden for now.
+    frame  $toplevel.output
+    frame  $toplevel.output.buttons
+    button $toplevel.output.buttons.hide   -text "Hide"    -command pkgconf::output_hide_window
+    button $toplevel.output.buttons.undock -text "Undock"  -command pkgconf::output_undock_window
+    button $toplevel.output.buttons.clear  -text "Clear"   -command pkgconf::output_clear_window
+    button $toplevel.output.buttons.save   -text "Save..." -command pkgconf::output_save
+    pack   $toplevel.output.buttons.hide         \
+	    $toplevel.output.buttons.undock      \
+	    $toplevel.output.buttons.clear       \
+	    $toplevel.output.buttons.save -side left -expand 1
+    pack   $toplevel.output.buttons -side top -fill x
+    text   $toplevel.output.text -wrap word -state disabled -yscrollcommand [list $toplevel.output.scroll set]
+    pack   $toplevel.output.text -side left -fill both -expand 1
+    scrollbar $toplevel.output.scroll -orient vertical -command [list $toplevel.output.text yview]
+    pack   $toplevel.output.scroll -side right -fill y
+
+    # Create a toplevel window matching the output frame, but leave it
+    # hidden for now. Give it the same bindings as the toplevel window.
+    toplevel .pkgconf_output -menu .menubar -class Pkgconf
+    wm withdraw .pkgconf_output
+    wm title    .pkgconf_output "pkgconf build output"
+    wm protocol .pkgconf_output WM_DELETE_WINDOW { pkgconf::handle_exit }
+    
+    bind .pkgconf_output <Alt-q> { pkgconf::handle_exit }
+    bind .pkgconf_output <Alt-f><KeyPress-x> { pkgconf::handle_exit }
+    
+    frame  .pkgconf_output.buttons
+    button .pkgconf_output.buttons.hide   -text "Hide"    -command pkgconf::output_hide_window
+    button .pkgconf_output.buttons.undock -text "Dock"    -command pkgconf::output_undock_window
+    button .pkgconf_output.buttons.clear  -text "Clear"   -command pkgconf::output_clear_window
+    button .pkgconf_output.buttons.save   -text "Save..." -command pkgconf::output_save
+    pack   .pkgconf_output.buttons.hide         \
+	    .pkgconf_output.buttons.undock      \
+	    .pkgconf_output.buttons.clear       \
+	    .pkgconf_output.buttons.save -side left -expand 1
+    pack   .pkgconf_output.buttons -side top -fill x
+    text   .pkgconf_output.text -wrap word -state disabled -yscrollcommand { .pkgconf_output.scroll set }
+    pack   .pkgconf_output.text -side left -fill both -expand 1
+    scrollbar .pkgconf_output.scroll -orient vertical -command { .pkgconf_output.text yview }
+    pack   .pkgconf_output.scroll -side right -fill y
+
+    efftcl::balloonhelp_for $toplevel.output.buttons.hide \
+	    "Remove the build output window, until more output appears."
+    efftcl::balloonhelp_for .pkgconf_output.buttons.hide \
+	    "Remove the build output window, until more output appears."
+    efftcl::balloonhelp_for $toplevel.output.buttons.undock \
+	    "Make the build output appear in a separate window."
+    efftcl::balloonhelp_for .pkgconf_output.buttons.undock \
+	    "Attach the build output to the main pkgconf window."
+    efftcl::balloonhelp_for $toplevel.output.buttons.clear \
+	    "Remove all build output text."
+    efftcl::balloonhelp_for .pkgconf_output.buttons.clear \
+	    "Remove all build output text."
+    efftcl::balloonhelp_for $toplevel.output.buttons.save \
+	    "Save build output to a file."
+    efftcl::balloonhelp_for .pkgconf_output.buttons.save \
+	    "Save build output to a file."
+
+    # Consult the options database.
+    variable output_docked
+    option add "*docked" 1 startupFile
+    set output_docked [option get $toplevel docked Pkgconf]
+
+    # Look for a file ~/.pkgconfrc and execute it.
+    if { [file isfile [file join "~" ".pkgconfrc"]] } {
+	if { [namespace eval :: { catch { source [file join "~" ".pkgconfrc"] } message } ] != 0 } {
+	    warning "The following error occurred while executing ~/.pkgconfrc: $message"
+	}
+    }
+}
+
+# ----------------------------------------------------------------------------
+# This routine creates the toplevel window, if pkgconf is running
+# independently rather than inside a larger application.
+
+proc pkgconf::create_toplevel_window { } {
+
+    variable toplevel
+    
+    # Start by creating the menubar. TK8's support for native menus is
+    # used.
+    menu .menubar -type menubar
+    menu .menubar.config -tearoff 0 -type normal
+    menu .menubar.edit   -tearoff 0 -type normal
+    menu .menubar.view   -tearoff 0 -type normal
+    menu .menubar.help   -tearoff 0 -type normal
+    
+    .menubar.config add command -label "Exit" -command pkgconf::handle_exit -underline 1 \
+	    -accelerator "Alt+Q"
+    
+    .menubar.edit add command -label "Undo" -state disabled
+    .menubar.edit add command -label "Redo" -state disabled
+    
+    .menubar.view add checkbutton -label "Show output" \
+	    -variable pkgconf::output_visible -onvalue 1 -offvalue 0 \
+	    -command pkgconf::output_menu
+    .menubar.view add checkbutton -label "Balloon help" \
+	    -variable pkgconf::efftcl::bhInfo(active) -onvalue 1 -offvalue 0
+    
+    .menubar.help add command -label "Help on pkgconf"    -state disabled
+    .menubar.help add command -label "Help on the system" -state disabled
+    .menubar.help add command -label "Search"             -state disabled
+    .menubar.help add separator
+    .menubar.help add command -label "About pkgconf" -state disabled
+    
+    .menubar add cascade -menu .menubar.config -label "Configuration" -underline 0
+    .menubar add cascade -menu .menubar.edit   -label "Edit"          -underline 0
+    .menubar add cascade -menu .menubar.view   -label "View"          -underline 0
+    .menubar add cascade -menu .menubar.help   -label "Help"          -underline 0
+
+    # Create the toplevel window, giving it a menubar and a name,
+    # and establish the accelerator binding.
+    toplevel $toplevel -menu .menubar -class Pkgconf
+    wm title $toplevel pkgconf
+    bind $toplevel <Alt-q> { pkgconf::handle_exit }
+    wm protocol $toplevel WM_DELETE_WINDOW { pkgconf::handle_exit }
+
+    # Although there is a Configuration menu instead of a file menu, it is
+    # desirable to support alt-f-x as a reasonably standard way of exiting
+    # the program.
+    bind $toplevel <Alt-f><KeyPress-x> { pkgconf::handle_exit }
+}
+
+# ----------------------------------------------------------------------------
+# This routine gets invoked for exit handling: when the user invokes the
+# exit entry on the file menu; or when the alt-q accelerator is used; or
+# when a window delete request is received. For now the code just exits,
+# but it might be an idea to offer to save configuration changes if any.
+#
+proc pkgconf::handle_exit { } {
+    exit 0
+}
+
+# }}}
+# {{{  Notebook pages                   
+
+# {{{  Targets page                     
+
+# ----------------------------------------------------------------------------
+# The targets page. This allows the user to select the target architecture,
+# platform, and startup. The range of available target architectures cannot
+# change while the program is running, so this window is actually fixed.
+#
+
+proc pkgconf::nbpage_targets_initialise { win } {
+
+    variable config_data
+    variable known_targets
+    variable target_data
+    variable target_page_data
+
+    # I need a variable that can be associated with the radiobuttons and
+    # identifies the selected target uniquely. It is not really
+    # possible to do this with the three variables in the config data
+    # directly.
+    set target_page_data(selection) [list $config_data(target) $config_data(platform) $config_data(startup)]
+    
+    # Conceivably the number of targets could be larger than can fit
+    # conveniently on to one screen, so it is desirable use a scrollable
+    # form. Start by creating a scrollbar, a canvas, and a frame within
+    # that canvas, and set up a standard resize callback. The canvas
+    # starts off small, but expands as necessary. The actual size will
+    # therefore be determined by whichever page does not have a
+    # vertical scrollbar, typically the build page. 
+    scrollbar $win.scrollbar -command "$win.viewport yview" -orient vertical
+    pack $win.scrollbar -side right -fill y
+
+    canvas $win.viewport -height 10m -yscrollcommand "$win.scrollbar set"
+    pack $win.viewport -fill both -expand 1 -padx 20 -pady 20
+
+    frame $win.viewport.form
+    $win.viewport create window 0 0 -anchor nw -window $win.viewport.form
+
+    bind $win.viewport.form <Configure> "pkgconf::nbpage_form_resized $win"
+
+    # Now fill in the form with details of all the targets.
+    # These are arranged in a simple grid.
+    set form $win.viewport.form
+    set row 0
+
+    foreach target $known_targets {
+
+	label $form.arch-$row -text "Target [get_target_alias $target]" -anchor w
+	grid  $form.arch-$row -row $row -column 0 -sticky w
+	incr row
+
+	foreach platform $target_data($target,known_platforms) {
+	    foreach startup $target_data($target,$platform,startup) {
+		
+		label $form.platform-$row -text "    Platform [get_platform_alias $target $platform]," -anchor w
+		grid  $form.platform-$row -row $row -column 0 -sticky w
+		label $form.startup-$row -text "$startup startup" -anchor w
+		grid  $form.startup-$row -row $row -column 1 -sticky w -ipadx 5
+		radiobutton $form.button-$row -variable pkgconf::target_page_data(selection) \
+			-value [list $target $platform $startup] \
+			-command pkgconf::clear_status_line
+		grid $form.button-$row -row $row -column 2 -sticky w
+		incr row
+	    }
+	}
+    }
+}
+
+#
+# The enter routine gets invoked when the user switches to the targets tab,
+# and also during start-up since the targets tab is the first one. It is
+# responsible for keeping track of the initial target selection, so that
+# when the user switches to a different tab it is possible to know whether
+# or not the target has actually changed.
+#
+# As a useful side effect, the routine updates the status line.
+#
+proc pkgconf::nbpage_targets_enter { win } {
+
+    variable config_data
+    variable target_page_data
+
+    set target_page_data(initial_selection) $target_page_data(selection)
+    
+    if { ($config_data(target) == "") || ($config_data(platform) == "") || ($config_data(startup) == "") } {
+	set_status_line "Please select a target system."
+    } else {
+	clear_status_line
+    }
+}
+
+proc pkgconf::nbpage_targets_leave { win } {
+
+    variable target_page_data
+
+    if { $target_page_data(selection) != $target_page_data(initial_selection) } {
+	variable config_data
+	variable build_tree_needs_update
+
+	set config_data(target)     [lindex $target_page_data(selection) 0]
+	set config_data(platform)   [lindex $target_page_data(selection) 1]
+	set config_data(startup)    [lindex $target_page_data(selection) 2]
+	set build_tree_needs_update 1
+
+	# A change of target has implications for the compiler flags, which
+	# must be handled immediately. The user may not go into the flags
+	# tab so the changes cannot be done in the flags tab enter/leave
+	# routines.
+	update_compiler_flags
+    }
+}
+
+#
+# This routine take care of resize operations. Whenever the size
+# of the form changes the canvas' scroll region needs to be adjusted.
+#
+# Note that this code is also used for the packages page.
+#
+proc pkgconf::nbpage_form_resized { win } {
+
+    set bbox      [$win.viewport bbox all]
+    set old_bbox  [$win.viewport cget -scrollregion]
+    set width     [winfo width $win.viewport.form]
+    set old_width [$win.viewport cget -width]
+    if { ("$bbox" != "$old_bbox") || ($width != $old_width) } {
+	$win.viewport configure -width $width -scrollregion $bbox
+    }
+}
+
+# }}}
+# {{{  Packages page                    
+
+# ----------------------------------------------------------------------------
+# The packages page. This is mostly similar to the targets page.
+# It is necessary to provide a mechanism for disabling packages.
+# It is also necessary to worry about changes to the target architecture.
+# The initialise() routine takes care of most of these things.
+
+proc pkgconf::nbpage_packages_initialise { win } {
+
+    variable config_data
+    variable known_packages
+    variable package_data
+    variable target_data
+    variable packages_page_data
+
+    # Start by storing details of the target that is currently
+    # being displayed.
+    set packages_page_data(current_target)   $config_data(target)
+    set packages_page_data(current_platform) $config_data(platform)
+    
+    # Typically the number of packages will be larger than can fit
+    # conveniently on to one screen, so it is desirable use a scrollable
+    # form. Start by creating a scrollbar, a canvas, and a frame within
+    # that canvas, and set up a standard resize callback. The canvas
+    # starts of small, but expands as necessary. The actual size will
+    # therefore be determined by whichever page does not have a
+    # vertical scrollbar, typically the build page.
+    scrollbar $win.scrollbar -command "$win.viewport yview" -orient vertical
+    pack $win.scrollbar -side right -fill y
+
+    canvas $win.viewport -height 10m -yscrollcommand "$win.scrollbar set"
+    pack   $win.viewport -fill both -expand 1 -padx 20 -pady 20
+
+    frame $win.viewport.form
+    $win.viewport create window 0 0 -anchor nw -window $win.viewport.form
+
+    bind $win.viewport.form <Configure> "pkgconf::nbpage_form_resized $win"
+
+    # Now fill in the form with details of all the packages.
+    # These are arranged in a simple grid.
+    set form $win.viewport.form
+    set row 0
+
+    # Now handle the packages themselves. Each package can be in one
+    # of the following states:
+    #
+    # 1) a hardware package is enabled by default if it is specified
+    #    for the current target or platform, disabled otherwise.
+    #    However the user should be able to explicitly enable or
+    #    disable it. This is handled by config_data(enabled_packages)
+    #    and config_data(disabled_packages).
+    #
+    # 2) a non-hardware package may be enabled or disabled by default,
+    #    but the user can change this. Again this is handled by
+    #    enabled_packages and disabled_packages.
+    #
+    # It is convenient to cache the list of target and platform specific
+    # packages.
+    set target_packages   ""
+    set platform_packages ""
+    if { $config_data(target) != "" } {
+	set target_packages $target_data($config_data(target),packages)
+	if { $config_data(platform) != "" } {
+	    set platform_packages $target_data($config_data(target),$config_data(platform),packages)
+	}
+    }
+    foreach pkg $known_packages {
+
+	# What is the current version of each package ?
+	set current_version ""
+
+	if { $package_data($pkg,hardware) != 0 } {
+	    if { ([lsearch -exact $target_packages $pkg] != -1) || ([lsearch -exact $platform_packages $pkg] != -1)} {
+		set current_version $config_data($pkg,version)
+	    }
+	} elseif { $package_data($pkg,default) != 0 } {
+	    set current_version $config_data($pkg,version)
+	}
+	
+	if { [lsearch -exact $config_data(enabled_packages) $pkg] != -1 } {
+	    set current_version $config_data($pkg,version)
+	}
+	if { [lsearch -exact $config_data(disabled_packages) $pkg] != -1 } {
+	    set current_version ""
+	}
+
+	# Store the current version, so that it is easier to figure out what
+	# has changed.
+	set packages_page_data($pkg,version) $current_version
+
+	label $form.label-$row -text "[get_package_alias $pkg]:"
+	grid  $form.label-$row -row $row -column 0 -sticky w
+	foreach version $package_data($pkg,versions) {
+	    label $form.version-$row -text "Version $version"
+	    grid  $form.version-$row -row $row -column 1 -sticky w -ipady 5
+
+	    radiobutton $form.button-$row -variable pkgconf::packages_page_data($pkg,version) \
+		    -value $version -command [list pkgconf::nbpage_packages_change $pkg]
+	    grid $form.button-$row -row $row -column 2 -sticky w
+	    incr row
+	}
+	label $form.version-$row -text "Disable"
+	grid  $form.version-$row -row $row -column 1 -sticky w
+	radiobutton $form.button-$row -variable pkgconf::packages_page_data($pkg,version) \
+		-value "" -command [list pkgconf::nbpage_packages_change $pkg]
+	grid $form.button-$row -row $row -column 2 -sticky w
+	incr row
+    }
+}
+
+#
+# This routine gets invoked whenever there is a change to one of the
+# packages. It sets a flag indicating that the build tree is out of data.
+# It will also update the appropriate config_data.
+#
+proc pkgconf::nbpage_packages_change { pkg } {
+
+    variable build_tree_needs_update
+    set build_tree_needs_update 1
+
+    variable packages_page_data
+    variable config_data
+    
+    set version $packages_page_data($pkg,version)
+    if { $version == "" } {
+	
+	# This package is being explicitly disabled.
+	set index [lsearch -exact $config_data(enabled_packages) $pkg]
+	if { $index != -1 } {
+	    set config_data(enabled_packages) [lreplace $config_data(enabled_packages) $index $index]
+	}
+
+	set index [lsearch -exact $config_data(disabled_packages) $pkg]
+	if { $index == -1 } {
+	    lappend config_data(disabled_packages) $pkg
+	}
+    } else {
+	
+	# A package version is being changed, possibly causing it to
+	# be enabled.
+	set config_data($pkg,version) $version
+
+	set index [lsearch -exact $config_data(disabled_packages) $pkg]
+	if { $index != -1 } {
+	    set config_data(disabled_packages) [lreplace $config_data(disabled_packages) $index $index]
+	}
+
+	set index [lsearch -exact $config_data(enabled_packages) $pkg]
+	if { $index == -1 } {
+	    lappend config_data(enabled_packages) $pkg
+	}
+    }
+}
+
+#
+# The packages page needs to be redrawn every time a different target is
+# selected, so that the user can select which version of the HAL packages
+# should be used. The redraw can be achieved by deleting the existing
+# contents of the window and re-invoking the initialise function.
+
+proc pkgconf::nbpage_packages_enter { win } {
+
+    variable packages_page_data
+    variable config_data
+    
+    if { ($packages_page_data(current_target)   != $config_data(target)) ||
+         ($packages_page_data(current_platform) != $config_data(platform)) } {
+	     
+	     foreach subwin [winfo children $win] {
+		 destroy $subwin
+	     }
+	     nbpage_packages_initialise $win
+    }
+
+    # If the target architecture is not yet known, remind the user.
+    if { ($config_data(target) == "") || ($config_data(platform) == "") || ($config_data(startup) == "") } {
+	set_status_line "Please select a target system."
+    } else {
+	clear_status_line
+    }
+}
+
+#
+# Currently this code does nothing, instead it is left to the
+# nbpage_packages_change routine above to update the appropriate
+# global information. This is unfortunate in that if the user
+# makes a mistake and undoes it pkgconf still thinks that the
+# build tree is out of date.
+
+proc pkgconf::nbpage_packages_leave { win } {
+}
+
+# }}}
+# {{{  Options page                     
+
+# ----------------------------------------------------------------------------
+# The options page. This consists of two frame arranged vertically.
+# The top frame allows the user to select the editor to use, which is
+# initialised from the VISUAL environment variable. The bottom frame
+# is another scrollable canvas allowing the user to select a header
+# file to edit.
+#
+# A number of special cases have to be allowed for. If there is no
+# text editor then all the buttons to edit a text file have to
+# be disabled. If there is no pkgconf directory in the build tree
+# yet then there will be no files to edit. Even if there is an
+# an pkgconf directory it may not contain all the files it is
+# supposed to, especially if the build tree is not up to date.
+#
+
+proc pkgconf::nbpage_options_initialise { win } {
+
+    # Start with the top frame. This contains a label, an entry field,
+    # and a button that invokes a file selector.
+    # NOTE: these borderwidths should not be hardwired.
+    variable editor
+    frame  $win.top -relief raised -borderwidth 2
+    label  $win.top.label  -text "Editor:"
+    entry  $win.top.entry  -relief sunken -borderwidth 2 -textvariable pkgconf::editor
+    button $win.top.button -text "Browse..." -command pkgconf::nbpage_options_browse
+    pack   $win.top.button -side right
+    pack   $win.top.entry  -side right -fill x -expand 1
+    pack   $win.top.label  -side left
+    pack   $win.top        -side top -fill x
+
+    efftcl::balloonhelp_for $win.top.entry  "The text editor that should be used."
+    efftcl::balloonhelp_for $win.top.button "Select the text editor that should be used."
+
+    # The entry widget does not support a command callback when
+    # a string has been typed in, so it is necessary to use the
+    # trace facility instead to enable or disable the buttons.
+    # Note that this routine may get invoked from the update routine,
+    # so it is necessary to clean up the old trace.
+    trace vdelete  pkgconf::editor w pkgconf::nbpage_options_editor_changed
+    trace variable pkgconf::editor w pkgconf::nbpage_options_editor_changed
+
+    # At this point there are now button widgets in this page. The
+    # nbpage_options_editor_changed routine used this list.
+    variable options_page_data
+    set options_page_data(buttons) ""
+    
+    # Now check for editable files in the pkgconf directory.
+    # Three of the files, makefile, pkgconf.mak and system.h are generated
+    # and should not be edited by users. There may also be various .stamp
+    # files used by the build system.
+    variable build_tree
+    set files ""
+    foreach filename [glob -nocomplain [file join $build_tree "pkgconf" "*"]] {
+	set filename [file tail $filename]
+	if { [string match {*.stamp} $filename] } continue
+	if { ($filename == "makefile") || ($filename == "pkgconf.mak") || ($filename == "system.h") } continue
+	lappend files $filename
+    }
+
+    if { $files == "" } {
+
+	# OK, just display a message. There is likely to be an appropriate
+	# warning in the message frame at the bottom of the window already.
+	label $win.message -text "There are no configuration files to edit."
+	pack  $win.message -fill both -expand 1
+
+	# A valid configuration always has a makevars file to edit, so the
+	# implication is that the build tree is out of date. It probably
+	# makes sense to set the relevant flag here.
+	variable build_tree_needs_update
+	set build_tree_needs_update 1
+	return
+    }
+
+    # The editable files are displayed in a scrollable frame, just
+    # like the targets and packages pages.
+    scrollbar $win.scrollbar -command "$win.viewport yview" -orient vertical
+    pack $win.scrollbar -side right -fill y
+
+    canvas $win.viewport -height 10m -yscrollcommand "$win.scrollbar set"
+    pack $win.viewport -fill both -expand 1 -padx 20 -pady 20
+
+    frame $win.viewport.form
+    $win.viewport create window 0 0 -anchor nw -window $win.viewport.form
+
+    bind $win.viewport.form <Configure> "pkgconf::nbpage_form_resized $win"
+
+    # Now fill in the form with all the buttons.
+    set form $win.viewport.form
+    set row 0
+
+    foreach file $files {
+
+	button $form.button-$row -text "<pkgconf/$file>" -anchor w \
+		-command [list pkgconf::nbpage_options_edit $file]
+	grid $form.button-$row -row $row -column 0 -sticky we -padx 10 -pady 5
+	label $form.label-$row -anchor w -text "Edit [file join $build_tree pkgconf $file]"
+	grid $form.label-$row -row $row -column 1 -sticky w
+
+	efftcl::balloonhelp_for $form.button-$row "Edit this configuration file."
+	lappend options_page_data(buttons) $form.button-$row
+	if { $editor == "" } {
+	    $form.button-$row configure -state disabled
+	}
+	incr row
+    }
+}
+
+# The options page needs to be redrawn every time the build tree is
+# regenerated, since this may result in a different set of editable
+# configuration header files.
+
+proc pkgconf::nbpage_options_enter { win } {
+
+    variable options_page_needs_refresh
+
+    if { $options_page_needs_refresh != 0 } {
+	set options_page_needs_refresh 0
+	foreach subwin [winfo children $win] { destroy $subwin }
+	nbpage_options_initialise $win
+    }
+
+    # It is also desirable to display some warnings.
+    variable config_data
+    variable build_tree_needs_update
+    variable editor
+    variable options_page_data
+
+    set options_page_data(no_editor) 0
+
+    if { ($config_data(target) == "") || ($config_data(platform) == "") || ($config_data(startup) == "") } {
+	set_status_line "The target hardware has not yet been selected."
+    } elseif { $build_tree_needs_update } {
+	set_status_line "The build tree needs updating."
+    } elseif { $editor == "" } {
+	set options_page_data(no_editor) 1
+	set_status_line "No text editor has been defined yet."
+    } else {
+	clear_status_line
+    }
+}
+
+#
+# There are no obvious actions to be taken if the user switches to
+# a different tab. Even if the user has invoked the editor on a
+# configuration file there is no guarantee that anything has actually
+# changed.
+proc pkgconf::nbpage_options_leave { win } {
+}
+
+#
+# This procedure allows the user to select a text editor. It gets invoked
+# from the browse button.
+#
+# NOTE: on windows it might be desirable to specify a .exe extension as
+# the type, but even that is optional under NT.
+#
+proc pkgconf::nbpage_options_browse { } {
+
+    variable editor
+    variable build_tree
+    variable toplevel
+    
+    set filename [tk_getOpenFile -initialdir $build_tree -parent $toplevel -title "Select text editor"]
+    if { $filename == "" } {
+	return
+    }
+    set editor $filename
+}
+
+#
+# This procedure is invoked whenever the editor variable is changed.
+# Its purpose is to enable or disable the buttons for the various
+# configuration headers.
+#
+# The code does not actually check whether the current editor value corresponds
+# to a valid executable. It is just about possible to check for an existing
+# file somewhere in the path, but that is expensive and there is still no
+# guarantee that the file is a valid executable.
+#
+
+proc pkgconf::nbpage_options_editor_changed { name1 name2 mode } {
+
+    variable editor
+    variable options_page_data
+    variable toplevel
+    
+    if { $editor == "" } {
+
+	foreach button $options_page_data(buttons) {
+	    $button configure -state disabled
+	}
+    } else {
+	if { $options_page_data(no_editor) } {
+	    $toplevel.message configure -text ""
+	    set options_page_data(no_editor) 0
+	}
+	foreach button $options_page_data(buttons) {
+	    $button configure -state normal
+	}
+    }
+}
+
+# This routine gets invoked to edit a header file. It simply
+# executes the current editor in the background. No attempt is
+# made to wait for command completion, to prevent a single file
+# from being edited multiple times, or anything like that.
+
+proc pkgconf::nbpage_options_edit { filename } {
+    
+    variable editor
+    variable build_tree
+
+    run_command pkgconf::nbpage_options_ignore $editor [file join $build_tree pkgconf $filename]
+}
+
+# This callback gets invoked from the run_command code whenever the
+# editor sends output. In the case of emacsclient such output
+# is the unfortunate string "Waiting for Emacs...Done". In the case
+# of xterm -e vi there is no output.
+#
+# On the whole it is better to ignore the output. This may mean
+# that warning or error messages get discared.
+
+proc pkgconf::nbpage_options_ignore { msg } {
+
+}
+
+# }}}
+# {{{  Flags page                       
+
+# ----------------------------------------------------------------------------
+# The flags page. This consists of a big grid, arranged vertically into
+# five separate categories of flags. Within each category there is a
+# label identifying the category and four separate entry fields for the
+# various variables.
+#
+# The data for a given flag can come from two sources. The default value
+# comes from the targets file, and in most cases this will be an empty
+# string. Alternatively the user can overwrite it, e.g. as a command
+# line argument or in the GUI, in which case the changed information
+# gets saved in the pkgconf save file.
+#
+
+proc pkgconf::nbpage_flags_initialise { win } {
+
+    variable flags_page_data
+    variable known_compiler_flags
+    variable config_data
+
+    # Start by creating array entries for all the known compiler flags.
+    if { ($config_data(target) != "") && ($config_data(platform) != "") } {
+	foreach flag $known_compiler_flags {
+	    set flags_page_data($flag) [get_platform_cflags $config_data(target) $config_data(platform) $flag]
+	}
+    } else {
+	foreach flag $known_compiler_flags {
+	    set flags_page_data($flag) ""
+	}
+    }
+
+    # If the current configuration information contains any compiler flags,
+    # update the appropriate variable.
+    foreach flag $known_compiler_flags {
+	if { [info exists config_data(cflags,$flag)] } {
+	    set flags_page_data($flag) $config_data(cflags,$flag)
+	}
+    }
+
+    # Keep track of which target and platform the current settings correspond to.
+    set flags_page_data(target)   $config_data(target)
+    set flags_page_data(platform) $config_data(platform)
+
+    # Time to actually create the interface.
+    # Again this tab uses a frame inside a scrolled canvas.
+    scrollbar $win.scrollbar -command "$win.viewport yview" -orient vertical
+    pack $win.scrollbar -side right -fill y
+
+    canvas $win.viewport -height 10m -yscrollcommand "$win.scrollbar set"
+    pack   $win.viewport -fill both -expand 1 -padx 20 -pady 20
+
+    frame $win.viewport.form
+    $win.viewport create window 0 0 -anchor nw -window $win.viewport.form
+
+    bind $win.viewport.form <Configure> "pkgconf::nbpage_form_resized $win"
+
+    # Now fill in the form with details of all the flags.
+    # These are arranged in a simple grid.
+    set form $win.viewport.form
+    set row 0
+
+    grid columnconfigure $form 1 -weight 1
+    label $form.archflags -text "Architecture-related compiler flags"
+    grid  $form.archflags -row $row -column 0 -columnspan 2 -sticky w 
+    incr row
+    foreach flag { ARCHFLAGS CARCHFLAGS CXXARCHFLAGS LDARCHFLAGS } {
+	label $form.label-$flag -text $flag
+	grid $form.label-$flag -row $row -column 0 -sticky w
+	entry $form.entry-$flag -textvariable pkgconf::flags_page_data($flag) -width 60
+	grid $form.entry-$flag -row $row -column 1 -sticky we
+	incr row
+    }
+
+    label $form.errflags -text "Warnings and errors"
+    grid  $form.errflags -row $row -column 0 -columnspan 2 -sticky w 
+    incr row
+    foreach flag { ERRFLAGS CERRFLAGS CXXERRFLAGS LDERRFLAGS } {
+	label $form.label-$flag -text $flag
+	grid $form.label-$flag -row $row -column 0 -sticky w
+	entry $form.entry-$flag -textvariable pkgconf::flags_page_data($flag)
+	grid $form.entry-$flag -row $row -column 1 -sticky we
+	incr row
+    }
+
+    label $form.dbgflags -text "Debugging and optimization levels"
+    grid  $form.dbgflags -row $row -column 0 -columnspan 2 -sticky w 
+    incr row
+    foreach flag { DBGFLAGS CDBGFLAGS CXXDBGFLAGS LDDBGFLAGS } {
+	label $form.label-$flag -text $flag
+	grid $form.label-$flag -row $row -column 0 -sticky w
+	entry $form.entry-$flag -textvariable pkgconf::flags_page_data($flag)
+	grid $form.entry-$flag -row $row -column 1 -sticky we
+	incr row
+    }
+
+    label $form.langflags -text "Language-related flags"
+    grid  $form.langflags -row $row -column 0 -columnspan 2 -sticky w 
+    incr row
+    foreach flag { LANGFLAGS CLANGFLAGS CXXLANGFLAGS LDLANGFLAGS } {
+	label $form.label-$flag -text $flag
+	grid $form.label-$flag -row $row -column 0 -sticky w
+	entry $form.entry-$flag -textvariable pkgconf::flags_page_data($flag)
+	grid $form.entry-$flag -row $row -column 1 -sticky we
+	incr row
+    }
+
+    label $form.extraflags -text "Miscellaneous"
+    grid  $form.extraflags -row $row -column 0 -columnspan 2 -sticky w 
+    incr row
+    foreach flag { EXTRAFLAGS CEXTRAFLAGS CXXEXTRAFLAGS LDEXTRAFLAGS } {
+	label $form.label-$flag -text $flag
+	grid $form.label-$flag -row $row -column 0 -sticky w
+	entry $form.entry-$flag -textvariable pkgconf::flags_page_data($flag)
+	grid $form.entry-$flag -row $row -column 1 -sticky we
+	incr row
+    }
+}
+
+#
+# There is nothing to be done on entry to this tab. If the target architecture
+# has changed then there will have been a separate call to update_compiler_flags
+# below. This cannot be delayed until the enter call because there is no
+# guarantee that the user will switch to the flags tab after changing target
+# architecture.
+#
+proc pkgconf::nbpage_flags_enter { win } {
+}
+
+#
+# On leaving the page it is necessary to check for any flags that are
+# different from the default settings, and update the config_data
+# settings appropriately.
+#
+proc pkgconf::nbpage_flags_leave { win } {
+
+    variable config_data
+    variable flags_page_data
+    variable known_compiler_flags
+    
+    if { ($config_data(target) == "") && ($config_data(platform) == "") } {
+	
+	# If no target has been defined then it is not possible to compare
+	# with the default setting. All that can be done is set or clear
+	# the current config setting.
+	foreach flag $known_compiler_flags {
+	    if { $flags_page_data($flag) == "" } {
+		if { [info exists config_data(cflags,$flag) ] } {
+		    unset config_data(cflags,$flag)
+		}
+	    } else {
+		set config_data(cflags,$flag) $flags_page_data($flag)
+	    }
+	}
+    } else {
+
+	# For any flag which currently has default settings,
+	# clear the appropriate config_data() entry. Otherwise store
+	# the flag data.
+	foreach flag $known_compiler_flags {
+
+	    if { $flags_page_data($flag) == [get_platform_cflags $config_data(target) $config_data(platform) $flag] } {
+		if { [info exists config_data(cflags,$flag) ] } {
+		    unset config_data(cflags,$flag)
+		}
+	    } else {
+		set config_data(cflags,$flag) $flags_page_data($flag)
+	    }
+	}
+    }
+}
+
+#
+# When the target architecture changes it is necessary to update the compiler
+# flags appropriately. Basically any flag that was defined by the old target
+# has to be cleared, and then any flag defined for the new target has to be
+# installed. There is a risk that some user changes will be lost, but there
+# is no easy solution to this.
+#
+proc pkgconf::update_compiler_flags { } {
+
+    variable config_data
+    variable flags_page_data
+    variable known_compiler_flags
+
+    foreach flag $known_compiler_flags {
+
+	if { ($flags_page_data(target) == "") || ($flags_page_data(platform) == "") } {
+	    set old_default_flag ""
+	} else {
+	    set old_default_flag [get_platform_cflags $flags_page_data(target) $flags_page_data(platform) $flag]
+	}
+	set new_default_flag [get_platform_cflags $config_data(target) $config_data(platform) $flag]
+
+	# If there is no change to the default flags leave things well alone.
+	# That way user changes will not get overwritten.
+	if { $old_default_flag == $new_default_flag } {
+	    continue
+	}
+
+	# Otherwise always install the new settings, which may mean eliminating
+	# user changes.
+	set flags_page_data($flag) $new_default_flag
+	if { [info exists config_data(cflags,$flag)] } {
+	    unset config_data(cflags,$flag)
+	}
+    }
+
+    set flags_page_data(target)   $config_data(target)
+    set flags_page_data(platform) $config_data(platform)
+}
+
+# }}}
+# {{{  Build page                       
+
+# ----------------------------------------------------------------------------
+# The build page. This consists of the following buttons, gridded,
+# with explanatory text.
+#
+# 1) Update build tree
+# 2) Make 
+# 3) Make tests
+# 4) Make clean
+#
+# The make buttons should be disabled if the build tree is out of date.
+# This is controlled by the variable build_tree_needs_update, which
+# may have got set during the options page creation. An additional
+# check that needs to be made here is whether or not the makefile
+# exists. If the current config data does not specify the target
+# then even the "Update tree" button must be disabled.
+#
+# Whenever a make is in progress all the buttons should be disabled.
+#
+
+proc pkgconf::nbpage_build_initialise { win } {
+
+    # Update the variable build_tree_needs_update if there is no
+    # makefile in the build tree.
+    variable build_tree_needs_update
+    variable component_repository
+    variable build_tree
+    variable config_data
+    
+    if { [file isfile [file join $build_tree makefile]] == 0 } {
+	set build_tree_needs_update 1
+    }
+    if { ($config_data(target) == "") || ($config_data(platform) == "") || ($config_data(startup) == "") } {
+	set build_tree_needs_update 1
+    }
+
+    # Create three labels identifying the various trees.
+    frame $win.top
+    label $win.top.source_label  -anchor w -text "Component repository:"
+    label $win.top.source_value  -anchor w -text "$component_repository"
+    label $win.top.build_label   -anchor w -text "Current build tree:"
+    label $win.top.build_value   -anchor w -text "$build_tree"
+    label $win.top.install_label -anchor w -text "Current install tree:"
+    label $win.top.install_value -anchor w -text "$config_data(prefix)"
+    grid  $win.top.source_label  -row 0 -column 0 -sticky w
+    grid  $win.top.source_value  -row 0 -column 1 -sticky we
+    grid  $win.top.build_label   -row 1 -column 0 -sticky w
+    grid  $win.top.build_value   -row 1 -column 1 -sticky we
+    grid  $win.top.install_label -row 2 -column 0 -sticky w
+    grid  $win.top.install_value -row 2 -column 1 -sticky we
+    grid  columnconfigure $win.top 1 -weight 1
+    pack  $win.top -side top -fill x -padx 5 -pady 5
+    
+    # Create a grid with the four buttons and with explanatory
+    # messages.
+    frame $win.grid
+    pack $win.grid -fill both -expand 1
+    set win $win.grid
+    
+    button $win.tree  -text "Update tree" -command pkgconf::nbpage_build_update_tree
+    button $win.make  -text "Make"        -command pkgconf::nbpage_build_make
+    button $win.tests -text "Make tests"  -command pkgconf::nbpage_build_tests
+    button $win.clean -text "Make clean"  -command pkgconf::nbpage_build_clean
+
+    if { ($config_data(target) == "" ) || ($config_data(platform) == "") || ($config_data(startup) == "") } {
+	$win.tree configure -state disabled
+    }
+    if { $build_tree_needs_update } {
+	$win.make  configure -state disabled
+	$win.tests configure -state disabled
+	$win.clean configure -state disabled
+    }
+    grid   $win.tree  -row 0 -column 0 -sticky we -padx 5 -pady 5
+    grid   $win.make  -row 1 -column 0 -sticky we -padx 5 -pady 5
+    grid   $win.tests -row 2 -column 0 -sticky we -padx 5 -pady 5
+    grid   $win.clean -row 3 -column 0 -sticky we -padx 5 -pady 5
+
+    message $win.treemsg -anchor w -justify left -width 300 -text \
+	    "Create or update the build and install trees."
+    message $win.makemsg -anchor w -justify left -width 300 -text \
+	    "Build the current configuration, leaving libraries and header files in the install tree."
+    message $win.testsmsg -anchor w -justify left -width 300 -text \
+	    "Build the test executables for the current configuration. These executables will be\
+	    placed in the install tree."
+    message $win.cleanmsg -anchor w -justify left -width 300 -text \
+	    "Perform a clean-up operation in the build and install trees."
+    grid $win.treemsg  -row 0 -column 1 -sticky we
+    grid $win.makemsg  -row 1 -column 1 -sticky we
+    grid $win.testsmsg -row 2 -column 1 -sticky we
+    grid $win.cleanmsg -row 3 -column 1 -sticky we
+    grid columnconfigure $win 1 -weight 1
+}
+
+# The build page needs refreshing every time that the user changes the
+# target or one of the packages. The important question is whether or
+# not the build tree needs to be updated before any builds can take place.
+# Of course the build tree can only be generated if the target architecture
+# is known.
+
+proc pkgconf::nbpage_build_enter { win } {
+
+    variable build_tree_needs_update
+    variable build_frame
+    variable toplevel
+    
+    if { $build_tree_needs_update } {
+	$build_frame.grid.make  configure -state disabled
+	$build_frame.grid.tests configure -state disabled
+	$build_frame.grid.clean configure -state disabled
+
+	set_status_line "The build tree must be updated before this configuration can be built."
+    } else {
+	clear_status_line
+    }
+
+    variable config_data
+    if { ($config_data(target) == "") || ($config_data(platform) == "") || ($config_data(startup) == "") } {
+	$build_frame.grid.tree configure -state disabled
+	set_status_line "The target hardware has not yet been selected."
+    } else {
+	$build_frame.grid.tree configure -state normal
+    }
+}
+
+#
+# There are no actual settings that can be changed in this tab,
+# so no updates are required.
+#
+proc pkgconf::nbpage_build_leave { win } {
+}
+
+#
+# This callback function is invoked during the various makes.
+# If it is passed an empty string then the command has finished and
+# all the buttons can be reenabled. Otherwise it is necessary to send
+# some text to the output window.
+
+proc pkgconf::nbpage_build_handle_output { mesg } {
+
+    if { $mesg == "" } {
+	output_add_text "Build has finished.\n"
+	nbpage_build_control_buttons 1
+    } else {
+	output_add_text "$mesg\n"
+    }
+}
+
+
+# This utility is used to control all the buttons in the build frame.
+proc pkgconf::nbpage_build_control_buttons { on } {
+
+    variable build_frame
+    set buttons [grid slaves $build_frame.grid -column 0]
+    foreach button $buttons {
+	if { $on } {
+	    $button configure -state normal
+	} else {
+	    $button configure -state disabled
+	}
+    }
+}
+
+#
+# These functions get invoked for the buttons in the build page.
+#
+proc pkgconf::nbpage_build_update_tree { } {
+
+    variable toplevel
+    variable options_page_needs_refresh
+    variable build_tree_needs_update
+    
+    nbpage_build_control_buttons 0
+    produce_build_tree
+    nbpage_build_control_buttons 1
+    $toplevel.message configure -text ""
+    set options_page_needs_refresh 1
+    set build_tree_needs_update    0
+}
+
+proc pkgconf::nbpage_build_make { } {
+
+    variable build_tree
+    variable config_data
+    
+    nbpage_build_control_buttons 0
+    output_add_text "Building current configuration.\nBuild tree is $build_tree.\nInstall tree is $config_data(prefix)\n"
+    run_command pkgconf::nbpage_build_handle_output make -C $build_tree
+}
+
+proc pkgconf::nbpage_build_tests { } {
+
+    variable build_tree
+    variable config_data
+    nbpage_build_control_buttons 0
+    output_add_text \
+	    "Building test executables.\nBuild tree is $build_tree\nInstall tree is $config_data(prefix)\n"
+    run_command pkgconf::nbpage_build_handle_output make -C $build_tree tests
+}
+
+proc pkgconf::nbpage_build_clean { } {
+
+    variable build_tree
+    nbpage_build_control_buttons 0
+    output_add_text "Cleaning build tree $build_tree.\n"
+    run_command pkgconf::nbpage_build_handle_output make -C $build_tree clean
+}
+
+# }}}
+
+# }}}
+# {{{  Output window                    
+
+# ----------------------------------------------------------------------------
+# These routines take care of the output window. There are two variables
+# describing the current state of the window: output_docked and
+# output_visible. There are four commands which can be invoked from
+# buttons, and a further command which can be invoked from the windows
+# menu. There is also a routine which can be used to add text to
+# the current output window, making it visible if necessary.
+#
+# The packing of the main .pkgconf window has to be redone every time
+# the output window is displayed or hidden, if the output window is
+# currently docked. To avoid duplicating code there are auxiliary
+# routine output_make_visible and output_make_invisible.
+#
+
+# This routine is invoked from the View menu. Its purpose is to
+# hide or display the output window, depending on the value of
+# output_visible. Note that output_visible indicates the desired
+# state of affairs, not the actual state of affairs.
+#
+proc pkgconf::output_menu { } {
+
+    variable output_visible
+
+    if { $output_visible == 0 } {
+	output_make_invisible
+    } else {
+	output_make_visible
+    }
+}
+
+proc pkgconf::output_make_visible { } {
+
+    variable output_docked
+    variable toplevel
+    
+    if { $output_docked == 0 } {
+	# The output is in a separate window. Deiconifying it should make
+	# it visible.
+	wm deiconify .pkgconf_output
+    } else {
+	# It is necessary to repack the main window. This time it is the
+	# output frame that should expand.
+	pack forget $toplevel.notebook $toplevel.message
+	pack $toplevel.notebook -side top -fill x
+	pack $toplevel.message  -side top -fill x
+	pack $toplevel.output   -side top -fill both -expand 1
+    }
+}
+
+proc pkgconf::output_make_invisible { } {
+
+    variable output_docked
+    variable toplevel
+    
+    if { $output_docked == 0 } {
+	# The output is in a separate window, just withdraw it.
+	wm withdraw .pkgconf_output
+    } else {
+	# It is necessary to repack the .pkgconf window without
+	# the output window. This time it is the notebook which
+	# should expand to fit all available space.
+	pack forget $toplevel.notebook $toplevel.message $toplevel.output
+	pack $toplevel.notebook -side top -fill both -expand 1
+	pack $toplevel.message -side top -fill x
+    }
+}
+
+#
+# This routine is invoked when the output window is already visible
+# and the user has pressed the button in that window to make it
+# invisible.
+#
+proc pkgconf::output_hide_window { } {
+
+    variable output_visible
+    variable toplevel
+    ASSERT { $output_visible != 0 }
+
+    output_make_invisible
+    set output_visible 0
+}
+
+#
+# Switch between a docked and an undocked window. The window must
+# currently be visible. Any text must be copied across from
+# one widget to the other.
+#
+proc pkgconf::output_undock_window { } {
+
+    variable output_visible
+    variable output_docked
+    variable toplevel
+
+    ASSERT { $output_visible != 0 }
+
+    output_make_invisible
+    
+    $toplevel.output.text configure -state normal
+    .pkgconf_output.text configure -state normal
+
+    if { $output_docked == 0 } {
+	$toplevel.output.text insert end [.pkgconf_output.text get 1.0 "end - 1 chars"]
+	.pkgconf_output.text delete 1.0 end
+	set output_docked 1
+    } else {
+	.pkgconf_output.text insert end [$toplevel.output.text get 1.0 "end - 1 chars"]
+	$toplevel.output.text delete 1.0 end
+	set output_docked 0
+    }
+
+    $toplevel.output.text configure -state disabled
+    .pkgconf_output.text configure -state disabled
+
+    output_make_visible
+}
+
+#
+# Clear the text window, which should currently be visible.
+# 
+proc pkgconf::output_clear_window { } {
+
+    variable output_visible
+    variable output_docked
+    variable toplevel
+
+    ASSERT { $output_visible != 0 }
+    if { $output_docked == 0 } {
+	.pkgconf_output.text configure -state normal
+	.pkgconf_output.text delete 1.0 end
+	.pkgconf_output.text configure -state disabled
+    } else {
+	$toplevel.output.text configure -state normal
+	$toplevel.output.text delete 1.0 end
+	$toplevel.output.text configure -state disabled
+    }
+}
+
+#
+# Save the current contents of the text window to a file.
+#
+proc pkgconf::output_save { } {
+
+    variable build_tree
+    variable output_docked
+    variable toplevel
+    
+    set filename [tk_getSaveFile -initialdir $build_tree -initialfile pkgconf.log \
+	    -parent [expr { $output_docked ? "$toplevel" : ".pkgconf_output" } ] ]
+    
+    if { $filename == "" } {
+	return
+    }
+    
+    if { [catch {
+	set file [open $filename w]
+	if { $output_docked == 0 } {
+	    puts $file [.pkgconf_output.text get 1.0 "end - 1 chars"]
+	} else {
+	    puts $file [$toplevel.output.text get 1.0 "end - 1 chars"]
+	}
+	close $file
+    } message] != 0 } {
+	warning "Failed to save build output to $filename: $message"
+    }
+}
+
+#
+# Output some text. This gets invoked mainly during builds, but also
+# by the report() function when generating a build tree. It is
+# necessary to make sure that the text widget is visible.
+#
+proc pkgconf::output_add_text { msg } {
+
+    variable output_visible
+    variable output_docked
+    variable toplevel
+    
+    if { $output_visible == 0 } {
+	output_make_visible
+	set output_visible 1
+    }
+
+    if { $output_docked == 0 } {
+	.pkgconf_output.text configure -state normal
+	.pkgconf_output.text insert end $msg
+	.pkgconf_output.text configure -state disabled
+	.pkgconf_output.text see end
+    } else {
+	$toplevel.output.text configure -state normal
+	$toplevel.output.text insert end $msg
+	$toplevel.output.text configure -state disabled
+	$toplevel.output.text see end
+    }
+}
+
+# }}}
+# {{{  Status line                      
+
+# ----------------------------------------------------------------------------
+# Some utility routines to update the status line.
+
+proc pkgconf::set_status_line { text } {
+    variable toplevel
+    $toplevel.message configure -text $text
+}
+
+proc pkgconf::clear_status_line { } {
+    variable toplevel
+    $toplevel.message configure -text ""
+}
+
+# }}}
+# {{{  Diagnostics                      
+
+# ----------------------------------------------------------------------------
+# GUI versions of the same routines. For now tk_messageBox will have
+# to suffice for fatal error 
+#
+proc pkgconf::gui_fatal_error_handler { msg } {
+
+    variable toplevel
+
+    if { [winfo exists $toplevel] } {
+	tk_messageBox -parent $toplevel -icon error -title "pkgconf: fatal error" -type ok -message $msg
+    } else {
+	tk_messageBox -icon error -title "pkgconf: fatal error" -type ok -message $msg
+    }
+    exit 1
+}
+
+proc pkgconf::gui_warning_handler { msg } {
+
+    variable toplevel
+
+    if { [winfo exists $toplevel] } {
+	tk_messageBox -parent $toplevel -icon error -title "pkgconf: fatal error" -type ok -message $msg
+    } else {
+	tk_messageBox -icon error -title "pkgconf: fatal error" -type ok -message $msg
+    }
+}
+
+proc pkgconf::gui_report_handler { msg } {
+
+    output_add_text "$msg\n"
+    update
+}
+
+# }}}
+# {{{  Executing external commands      
+
+# ----------------------------------------------------------------------------
+# This code is responsible for executing external commands. The broken nature
+# of pipes under Windows makes this somewhat more difficult than it should be.
+
+proc pkgconf::run_command { callback command args } {
+
+    # Start up the specified command. Actually the main command is run such that
+    # both stdout and stderr are piped into cat, thus making sure that the
+    # stderr output is not discarded.
+    set result [catch {	set pipe [open  "|[concat $command $args] 2>@ stdout" "r"] } message]
+    if { $result != 0 } {
+	warning "Failed to execute $command: $message"
+	return
+    }
+
+    # Now set the pipe to non-blocking, to prevent the whole GUI from
+    # locking up by accident.
+    fconfigure $pipe -blocking 0
+
+    # Set up an event handler to read back any output.
+    fileevent $pipe readable [list pkgconf::handle_command_output $pipe $callback]
+}
+
+#
+# This routine gets invoked whenever there may be data waiting on the
+# pipe.
+#
+proc pkgconf::handle_command_output { pipe callback } {
+
+    set data  ""
+    set count [gets $pipe data]
+    if { $count > 0 } {
+	$callback $data
+    } elseif { [eof $pipe] } {
+	catch { close $pipe }
+	$callback ""
+    }
+}
+
+# }}}
+
+# }}}
+# {{{  Produce build tree               
+
+# {{{  Description                      
+
+# ----------------------------------------------------------------------------
+# Producing the build tree. This requires the following information:
+#
+# 1) the location of the component repository. This should be known.
+#
+# 2) the location of the build tree. This should also be known.
+#
+# 3) full details of the target architecture. These may not have been supplied
+#    yet, which would result in a fatal error.
+#
+# 4) details of which packages are explicitly enabled or disabled.
+#
+# 5) details of which version of each package should be used.
+#
+# 6) prefix information, i.e. where the system will be installed.
+#    The default value "" indicates an install directory relative to the
+#    build tree.
+#
+# This routine may be called for an empty build tree, or it
+# may be used to update an existing build tree.
+#
+# The steps that have to be taken are as follows:
+#
+# 1) create a pkgconf directory, which is where all the editable
+#    header files will end up. A few other files will end up there
+#    as well.
+#
+# 2) examine all packages, including the hal packages. For each package:
+#
+#    a) if there is a src directory in the component repository, reproduce
+#       this in the build tree. If this src directory has any
+#       sub-directories, these have to be created as well.
+#
+#       The src directory should contain a file PKGconf.mak. This has to be
+#       copied across as makefile. There may also be PKGconf.mak files in
+#       subdirectories which should also get copied across. Typically the
+#       latter will be for internal development purposes.
+#
+#    b) if there is an include directory in the component repository,
+#       make a matching directory in the build tree. Ditto for any
+#       subdirectories other than pkgconf.
+#
+#       The purpose of this include directory is to allow users to make
+#       editable copies of the header files in the build tree. A makefile
+#       is generated in this directory which will take care of updating the
+#       header files that actually get used for building, i.e. those in the
+#       include directory. The header files are not actually copied across
+#       at this point, that will happen during the first make.
+#
+#       The pkgconf directory is handled differently. Every file in
+#       pkgconf is copied across to the build tree's pkgconf directory,
+#       unless there is already a matching file in the build tree.
+#       This avoids overwriting edits made by the user in the build
+#       tree. The disadvantage is that if the version in the component
+#       repository is edited it will have to be copied by hand, or the
+#       -force command line argument will have to be used.
+#
+#    c) if there is a tests directory in the component repository, make
+#       a matching directory in the build tree and copy
+#       across PKGconf.mak. If this tests directory has any subdirectories
+#       then these must be created as well.
+#
+# 3) create the install directory tree, complete with lib, include, and tests
+#    directories, and the include/pkgconf sub-directory. Additional
+#    sub-directories within include need not be generated at this point,
+#    that happens as such sub-directories are encountered within the
+#    component repository.
+#
+# 4) copy across the makevars file.
+#
+# 5) generate the files specific to this configuration.
+#
+# Note that subdirectories corresponding to old packages are not
+# deleted, they simply get ignored. Arguably this should change,
+# but then you run the risk of deleting user changes in a package
+# that has been disabled only temporarily.
+#
+
+# }}}
+# {{{  Check requirements               
+
+# ----------------------------------------------------------------------------
+# It is not possible to generate a build tree unless certain requirements
+# are satisfied. Most of these will have been checked elsewhere.
+#
+proc pkgconf::check_requirements { } {
+
+    ASSERT { $pkgconf::build_tree != "" }
+    ASSERT { $pkgconf::component_repository != "" }
+
+    if { $pkgconf::component_repository == $pkgconf::build_tree } {
+	report "Builds of eCos cannot happen in the same directory as the sources."
+	report "Currently your build tree is $pkgconf::build_tree."
+	report "This is the same directory as your component repository."
+	fatal_error "Unable to proceed with builds in this directory."
+    }
+
+    if { $pkgconf::config_data(target) == "" } {
+	fatal_error "No target architecture has been specified."
+    }
+    if { $pkgconf::config_data(platform) == "" } {
+	fatal_error "No target platform has been specified."
+    }
+    if { $pkgconf::config_data(startup) == "" } {
+	fatal_error "No target startup mechanism has been specified."
+    }
+}
+
+# }}}
+# {{{  Directory and file utilities     
+
+# ----------------------------------------------------------------------------
+# Start with a number of utility routines to access all files in
+# a directory, stripping out well-known files such as makefile.am.
+# The routines take an optional pattern argument if only certain
+# files are of interest.
+#
+# Note that symbolic links are returned as well as files.
+#
+proc pkgconf::locate_files { dir { pattern "*"} } {
+
+    ASSERT { $dir != "" }
+
+    # Start by getting a list of all the files.
+    set filelist [glob -nocomplain -- [file join $dir $pattern]]
+
+    # Eliminate the pathnames from all of these files
+    set filenames ""
+    foreach file $filelist {
+	lappend filenames [file tail $file]
+    }
+
+    # Eliminate any obviously spurious entries.
+    foreach file { CVS Makefile.am Makefile.in makefile acinclude.m4 aclocal.m4 configure.in configure } {
+	set index [lsearch -exact $filenames $file]
+	if { $index != -1 } {
+	    set filenames [lreplace $filenames $index $index]
+	}
+    }
+
+    # Eliminate any subdirectories.
+    set subdirs ""
+    foreach name $filenames {
+	if { [file isdir [file join $dir $name]] } {
+	    lappend subdirs $name
+	}
+    }
+    foreach subdir $subdirs {
+	set index [lsearch -exact $filenames $subdir]
+	set filenames [lreplace $filenames $index $index]
+    }
+
+    return $filenames
+}
+
+#
+# This utility returns all sub-directories, as opposed to all files.
+# A variant glob pattern is used here. This version is not recursive.
+proc pkgconf::locate_subdirs { dir { pattern "*" }} {
+
+    ASSERT { $dir != "" }
+
+    set dirlist [glob -nocomplain -- [file join $dir $pattern "."]]
+
+    # Eliminate the pathnames and the spurious /. at the end of each entry
+    set dirnames ""
+    foreach dir $dirlist {
+	lappend dirnames [file tail [file dirname $dir]]
+    }
+
+    # Get rid of the CVS directory, if any
+    set index [lsearch -exact $dirnames "CVS"]
+    if { $index != -1 } {
+	set dirnames [lreplace $dirnames $index $index]
+    }
+
+    # That should be it.
+    return $dirnames
+}
+
+#
+# A variant which is recursive. This one does not support a pattern.
+#
+proc pkgconf::locate_all_subdirs { dir } {
+
+    ASSERT { $dir != "" }
+
+    set result ""
+    foreach subdir [locate_subdirs $dir] {
+	lappend result $subdir
+	foreach x [locate_all_subdirs [file join $dir $subdir]] {
+	    lappend result [file join $subdir $x]
+	}
+    }
+    return $result
+}
+
+#
+# This routine returns a list of all the files in a given directory and in
+# all subdirectories, preserving the subdirectory name.
+#
+proc pkgconf::locate_all_files { dir { pattern "*" } } {
+
+    ASSERT { $dir != "" }
+
+    set files   [locate_files $dir $pattern]
+    set subdirs [locate_subdirs $dir]
+
+    foreach subdir $subdirs {
+	set subfiles [locate_all_files [file join $dir $subdir] $pattern]
+	foreach file $subfiles {
+	    lappend files [file join $subdir $file]
+	}
+    }
+
+    return $files
+}
+
+#
+# Sometimes a directory may be empty, or contain just a CVS subdirectory,
+# in which case there is no point in copying it across.
+#
+proc pkgconf::is_empty_directory { dir } {
+
+    ASSERT { $dir != "" }
+
+    set contents [glob -nocomplain -- [file join $dir "*"]]
+    if { [llength $contents] == 0 } {
+	return 1
+    }
+    if { ([llength $contents] == 1) && [string match {*CVS} $contents] } {
+	return 1
+    }
+    return 0
+}
+
+# ----------------------------------------------------------------------------
+# Given a pathname such as kernel/current/include, this routine returns
+# a string which points at the build tree's pkgconf directory
+# complete with the appropriate number of ..'s in the pathname
+#
+proc pkgconf::get_dotdots_pkgconf { dir } {
+
+    set result ".."
+
+    while { 1} {
+	set dir [file dirname $dir]
+	if { ($dir == ".") || ($dir == ":") } {
+	    break
+	}
+	set result [file join $result ".."]
+    }
+
+    set result [file join $result "pkgconf"]
+    return $result
+}
+
+# ----------------------------------------------------------------------------
+# Internally this script works using native path names. When outputting the
+# makefile information it is necessary to output the paths in a format that
+# is understood by GnuMake.
+#
+proc pkgconf::get_pathname_for_make { dir } {
+
+    # First of all, handle directory separators. Under Unix this is a no-op.
+    # Under Windows the current directory separator may be \ instead of /.
+    # It is not entirely clear under what circumstances Tcl's "file nativename"
+    # facility uses / vs. \, but converting all existing backslashes to
+    # forward slashes should be harmless.
+    if { $pkgconf::windows_host != 0 } {
+	regsub -all -- {\\} $dir "/" dir
+    }
+
+    # Next cope with spaces in pathnames. GnuMake does not always cope
+    # sensibly with these, even if backslash quoting is used - there
+    # are known problems with VPATH and with the wildcard function. Under
+    # Unix there is no simple solution, but it is unlikely that the problem
+    # will arise in the first place. Under Windows it is quite likely that
+    # the problem will arise, but it is possible to work around it
+    # by using a short file name.
+    
+    set index [string first " " $dir]
+    if { $index != -1 } {
+	if { $pkgconf::windows_host != 0 } {
+
+	    # This call will only work if the specified object already exists.
+	    # This should not be a problem, as the pathnames are likely to
+	    # be relative to the component repository, the build tree, or the
+	    # install tree. The component repository must already exist. The
+	    # build tree must already exist, since this call will only be
+	    # invoked when generating makefiles inside the build tree. The
+	    # install tree will have been created before pkgconf/pkgconf.mak
+	    # is written out, and that is the only place that needs to refer
+	    # to it.
+
+	    set dir [file attribute $dir -shortname]
+	} else {
+	    fatal_error "Cannot cope with pathname \"$pathname\".\nPathnames with spaces are not supported on this platform."
+	}
+    }
+
+    # Other characters that have special meaning include: []*?()$^|&#<>"'`;
+    # At least, that is the list used within vmake. $ has to be quoted as $$,
+    # the others should be quoted with a single backslash.
+    regsub -all -- {\$} $dir {$$} dir
+    foreach char { {\[} {\]} {\*} {\?} {\(} {\)} {\^} {\|} {\&} {\#} {\<} {\>} {\"} {\'} {\`} {\;} } {
+	regsub -all -- $char $dir "\\$char" dir
+    }
+
+    # Now cope with Windows drive names.
+    #
+    # First, volume-relative filenames. If the directory string begins with
+    # c:xxx, as opposed to c:/xxx, it is necessary to figure out what the
+    # current directory is on drive c: and use this as prefix for the rest
+    # of the string.
+    #
+    # Once volume-relative filenames have been handled the directory may be
+    # along the lines of c:/Cygnus/eCos/whatever. This is problematical for
+    # GnuMake since the colon character has special meaning in various places,
+    # e.g. to identify static pattern rules. cygwin32 uses //c/ as an alias
+    # for c:/ wherever appropriate.
+    if { $pkgconf::windows_host != 0 } {
+
+	set dummy  ""
+	set match1 ""
+	set match2 ""
+	set match3 ""
+	
+	if { [regexp -- {^([a-zA-Z]:)(/)?(.*)} $dir dummy match1 match2 match3] == 1 } {
+	    if { $match2 == "" } {
+		set result [catch {
+		    set current_dir [pwd]
+		    cd $match1
+		    set dir [pwd]
+		    regsub -all -- {\\} $dir "/" dir
+		    set dir [file join $dir $match3]
+		    cd $current_dir
+		} message]
+  	        if { $result != 0 } {
+		    fatal_error "Failed to determine current directory on drive $match1"
+		}
+	    }
+	}
+
+	if { [regexp -- {^([a-zA-Z]):} $dir dummy match1] == 1 } {
+	    set dir "//$match1[string range $dir 2 end]"
+	}
+    }
+    
+    return $dir
+}
+
+# ----------------------------------------------------------------------------
+# Take a cygwin32 filename such as //d/tmp/pkgobj and turn it into something
+# acceptable to Tcl, i.e. d:/tmp/pkgobj. There are a few other complications...
+
+proc pkgconf::get_pathname_for_tcl { name } {
+    
+    if { $pkgconf::windows_host } {
+	set dummy  ""
+	set match1 "" 
+	set match2 ""
+
+	# If the pathname is relative to ~, expand it now.
+	if { [regexp -- {^~(.*)$} $name dummy match1] } {
+	    set name [file nativename $name]
+	}
+	
+	# For consistency, get rid of any backslashes in the pathname.
+	# Especially since the file nativename command above may have
+	# introduced some.
+	regsub -all -- {\\} $name "/" name
+
+	# And now replace //d/tmp/pkgobj with d:/tmp/pkgobj
+	if { [regexp --  {^//([a-zA-Z])(/.*)$} $name dummy match1 match2] == 1 } {
+	    set name "$match1:$match2"
+	}
+
+	# If the pathname is relative to the cygwin root, get hold of the
+	# drive name.
+	if { ([regexp -- {^/(.*)$} $name dummy match1] == 1) && ([regexp -- {^//.*$} $name dummy] == 0) } {
+	    set saved_dir [pwd]
+	    cd /
+	    set name [file join [pwd] $match1]
+	    cd $saved_dir
+	}
+    }
+    return $name
+}
+
+# ----------------------------------------------------------------------------
+# Output a standard banner to a generated file.
+#
+
+proc pkgconf::output_banner { file name } {
+
+    puts $file \
+"#
+# File $name
+#
+# This file is generated automatically by the pkgconf program.
+# It should not be edited. Any changes made to this file may
+# be overwritten by pkgconf.
+#"
+}
+
+# ----------------------------------------------------------------------------
+# Make sure that a newly created or copied file is writable. This operation
+# is platform-specific. Under Unix at most the current user is given
+# permission, since there does not seem to be any easy way to get hold
+# of the real umask.
+
+proc pkgconf::make_writable { name } {
+
+    ASSERT { $name != "" }
+    ASSERT { [file isfile $name] }
+    
+    if { [file writable $name] == 0 } {
+	if { $pkgconf::windows_host != 0 } {
+	    file attributes $name -readonly 0
+	} else {
+	    set mask [file attributes $name -permissions]
+	    set mask [expr $mask | 0200]
+	    file attributes $name -permissions $mask
+	}
+    }
+}
+
+# }}}
+# {{{  Other utilities                  
+
+# ----------------------------------------------------------------------------
+# This code works out what packages should go into the current configuration.
+# The rules are as follows:
+#
+# 1) all hardware-specific packages should be disabled, unless they are
+#    mentioned explicitly in the targets file for the current target or
+#    platform, or unless they are enabled explicitly by the user.
+#
+# 2) all other packages which are enabled by default should be included,
+#    unless they are disabled explicitly by the user.
+#
+# 3) all non-hardware packages which are disabled by default should be
+#    left out, unless they are enabled explicitly by the user.
+
+proc pkgconf::get_current_packages { } {
+
+    ASSERT { $pkgconf::config_data(target)   != "" }
+    ASSERT { $pkgconf::config_data(platform) != "" }
+    ASSERT { [info exists pkgconf::target_data($pkgconf::config_data(target),packages)] }
+    ASSERT { [info exists pkgconf::target_data($pkgconf::config_data(target),$pkgconf::config_data(platform),packages)] }
+    ASSERT { [info exists pkgconf::config_data(enabled_packages)] }
+    ASSERT { [info exists pkgconf::config_data(disabled_packages)] }
+    
+    set result ""
+    set target_packages   $pkgconf::target_data($pkgconf::config_data(target),packages)
+    set platform_packages $pkgconf::target_data($pkgconf::config_data(target),$pkgconf::config_data(platform),packages)
+    set enabled_packages  $pkgconf::config_data(enabled_packages)
+    set disabled_packages $pkgconf::config_data(disabled_packages)
+    
+    foreach pkg $pkgconf::known_packages {
+
+	ASSERT { [info exists pkgconf::package_data($pkg,hardware)] }
+	
+	if { $pkgconf::package_data($pkg,hardware) } {
+
+	    if { [lsearch -exact $disabled_packages $pkg] != -1 } {
+		continue
+	    } elseif { [lsearch -exact $target_packages $pkg] != -1 } {
+		lappend result $pkg
+	    } elseif { [lsearch -exact $platform_packages $pkg] != -1 } {
+		lappend result $pkg
+	    } elseif { [lsearch -exact $enabled_packages $pkg] != -1 } {
+		lappend result $pkg
+	    }
+
+	} elseif { $pkgconf::package_data($pkg,default) != 0 } {
+	    if { [lsearch -exact $disabled_packages $pkg] == -1 } {
+		lappend result $pkg
+	    }
+	} else {
+	    if { [lsearch -exact $enabled_packages $pkg] != -1 } {
+		lappend result $pkg
+	    }
+	}
+    }
+
+    return $result
+}
+
+# ----------------------------------------------------------------------------
+# A utility to determine whether or not a particular setting has changed relative to
+# what is currently in the build tree. Information about the latter comes from
+# the save file, but may not exist the first time that the build tree is generated.
+# The config_data array itself may be sparsely populated, especially when it comes
+# to compiler flags.
+
+proc pkgconf::value_has_changed { name } {
+
+    ASSERT {$name != ""}
+
+    if { [info exists pkgconf::config_data($name)] == 0 } {
+
+	if { [info exists pkgconf::saved_data($name)] == 0 } {
+	    return 0
+	} else {
+	    return 1
+	}
+	
+    } else {
+
+	if { [info exists pkgconf::saved_data($name)] == 0 } {
+	    return 1
+	}
+	if { $pkgconf::saved_data($name) != $pkgconf::config_data($name) } {
+	    return 1
+	}
+	return 0
+    }
+}
+
+# ----------------------------------------------------------------------------
+# Has there been a change in the packages in the system, either which packages
+# are present or any of their versions ? If there has been a change in the
+# packages then the amount of work that has to be done inside pkgconf is
+# increased significantly.
+
+proc pkgconf::packages_have_changed { } {
+    
+    if { [value_has_changed "target"] || [value_has_changed "platform"] } {
+	return 1
+    }
+    if { [value_has_changed "enabled_packages"] || [value_has_changed "disabled_packages"] } {
+	return 1
+    }
+    foreach pkg $pkgconf::known_packages {
+	if { [value_has_changed "$pkg,version"] } {
+	    return 1
+	}
+    }
+
+    return 0
+}
+
+
+# ----------------------------------------------------------------------------
+# Given the name of a package, e.g. CYGPKG_KERNEL, return something which is
+# more acceptable in the build tree. Creating directories and files with
+# names containing lots of upper case characters is unacceptable.
+
+proc pkgconf::get_build_alias { name } {
+
+    ASSERT { $name != ""}
+
+    set index [string first "_" $name] 
+    if { $index != -1 } {
+	set new_name [string range $name [incr index] end]
+	if { $new_name != "" } {
+	    set name $new_name
+	}
+    }
+    set name [string tolower $name]
+    return $name
+}
+
+# }}}
+# {{{  Process a package                
+
+# ----------------------------------------------------------------------------
+# Process an entire package. The header files, the src directory, and the
+# tests directory are all handled separately.
+
+proc pkgconf::process_package { name dir } {
+
+    report "Processing package $dir"
+    
+    set incdir [file join $dir "include"]
+    if { [file isdir [file join $pkgconf::component_repository $incdir]] } {
+	lappend pkgconf::inc_dirs $incdir
+	process_package_includes $name $incdir
+    }
+
+    set srcdir [file join $dir "src"]
+    if { [file isdir [file join $pkgconf::component_repository $srcdir]] } {
+	if { [file isfile [file join $pkgconf::component_repository $srcdir PKGconf.mak]] } {
+	    lappend pkgconf::src_dirs $srcdir
+	    process_package_src $srcdir
+	}
+    }
+
+    set testsdir [file join $dir "tests"]
+    if { [file isdir [file join $pkgconf::component_repository $testsdir]] } {
+	if { [file isfile [file join $pkgconf::component_repository $testsdir PKGconf.mak]] } {
+	    lappend pkgconf::tests_dirs $testsdir
+	    process_package_tests $name $testsdir
+	} 
+    }
+}
+
+# ----------------------------------------------------------------------------
+# Processing the header files is somewhat complicated. The include directories
+# and any subdirectories except pkgconf have to be created in the build tree.
+# Details of these subdirectories get stored in the variable header_subdirs.
+# A pro-forma makefile has to be generated in the build tree, using details
+# of all the header files. These header files must also be appended to the
+# variable header_files. Any files in pkgconf have to be copied across
+# to the pkgconf directory, but must not overwrite the files that are
+# already there.
+#
+# As an additional complication, each package may define its own header
+# file directory. For example a package xyz may specify that all of its
+# exported header files should go into the xyz subdirectory of the install
+# include tree, not at the top level. There are special cases for the HAL.
+#
+# Note that if there are any spurious directories already present, these
+# do not get deleted. However the generated makefile will not reference
+# them so it should be harmless.
+#
+
+proc pkgconf::process_package_includes { name dir } {
+
+    # What subdirectory in the install tree should be used ?
+    ASSERT { [info exists pkgconf::package_data($name,incdir)] }
+    set pkg_incdir $pkgconf::package_data($name,incdir)
+
+    # Get information about the package's include directory.
+    set pkg_inc_dirs      [locate_all_subdirs [file join $pkgconf::component_repository $dir]]
+    set pkg_pkgconf_files [locate_files       [file join $pkgconf::component_repository $dir "pkgconf"]]
+    set pkg_inc_files     [locate_all_files   [file join $pkgconf::component_repository $dir]]
+
+    # Remove all pkgconf headers from the all_headers list. pkgconf headers
+    # are handled by a separate makefile in the pkgconf directory.
+    foreach hdr $pkg_pkgconf_files {
+	set index [lsearch -exact $pkg_inc_files [file join pkgconf $hdr]]
+	ASSERT { $index != -1 }
+	set pkg_inc_files [lreplace $pkg_inc_files $index $index]
+    }
+
+    # Remove all unwanted subdirectories. These include pkgconf (which is handled
+    # specially) and any empty subdirectories.
+    set tmp ""
+    foreach subdir $pkg_inc_dirs {
+	if { ($subdir == "pkgconf") } {
+	    continue
+	}
+	if { [is_empty_directory [file join $pkgconf::component_repository $dir $subdir] ] } {
+	    continue
+	}
+	lappend tmp $subdir
+    }
+    set pkg_inc_dirs $tmp
+    
+    # Create the include directory in the build tree, plus any subdirectories.
+    # This directory tree must map the structure of the source tree, so there is
+    # no need to worry about thispkg_incdir.
+    file mkdir [file join $pkgconf::build_tree $dir]
+    foreach subdir $pkg_inc_dirs {
+	file mkdir [file join $pkgconf::build_tree $dir $subdir]
+    }
+
+    # Copy across the pkgconf files that are not already present.
+    # Also keep track of this information globally.
+    foreach hdr $pkg_pkgconf_files {
+	if { [lsearch -exact $pkgconf::pkgconf_files $hdr] != -1 } {
+	    return -code error "Duplicate configuration header file $hdr"
+	}
+	lappend pkgconf::pkgconf_files $hdr
+	
+	set src  [file join $pkgconf::component_repository $dir "pkgconf" $hdr]
+	set dest [file join $pkgconf::build_tree "pkgconf" $hdr]
+	if { (! [file isfile $dest]) || ($pkgconf::force_arg != 0) } {
+	    file copy -force -- $src $dest
+	    make_writable $dest
+	}
+    }
+
+    # Keep track of all the subdirectories that should be generated
+    # in the install tree. Note that packages may share these
+    # subdirectories, e.g. the sys subdirectory will be used by
+    # both the C library and by a POSIX compatibily layer.
+    #
+    # It is necessary to list pkg_incdir separately, and all
+    # of its parent directories. Otherwise it is not possible to
+    # clean up the install tree correctly when packages get removed.
+    if { $pkg_incdir != "" } {
+	set current_path ""
+	foreach field [file split $pkg_incdir] {
+	    set current_path [file join $current_path $field]
+	    if { [lsearch -exact $pkgconf::install_inc_dirs $current_path] == -1 } {
+		lappend pkgconf::install_inc_dirs $current_path
+	    }
+	}
+    }
+    foreach subdir $pkg_inc_dirs {
+	set dest [file join $pkg_incdir $subdir]
+	if { [lsearch -exact $pkgconf::install_inc_dirs $dest] == -1 } {
+	    lappend pkgconf::install_inc_dirs $dest
+	}
+    }
+    
+    # Keep track of all header files, checking for duplicates.
+    foreach hdr $pkg_inc_files {
+	set dest [file join $pkg_incdir $hdr]
+	if { [lsearch -exact $pkgconf::install_inc_files $dest] != -1 } {
+	    return -code error "Duplicate header file $dest" 
+	}
+	lappend pkgconf::install_inc_files $dest
+    }
+
+    # Generate the makefile in the target directory. First set up
+    # some variables to contain the right sort of data for a makefile.
+    if { $pkg_incdir == "" } {
+	set pkg_incdir "."
+    } else {
+	set pkg_incdir [get_pathname_for_make $pkg_incdir]
+    }
+    set tmp ""
+    foreach file $pkg_inc_files {
+	lappend tmp [get_pathname_for_make $file]
+    }
+    set pkg_inc_files $tmp
+
+    set filename [file join $dir "makefile"]
+    set makefile [open [file join $pkgconf::build_tree $filename] "w"]
+    output_banner $makefile $filename
+    puts $makefile \
+"
+include [get_pathname_for_make [file join [get_dotdots_pkgconf $dir] "pkgconf.mak"]]
+
+HEADERS := $pkg_inc_files
+TARGETS := \$(foreach hdr,\$(HEADERS),\$(PREFIX)/include/$pkg_incdir/\$(hdr))
+.PHONY  : build clean
+VPATH   := . [file join \$(COMPONENT_REPOSITORY) $dir]
+
+build: \$(TARGETS)
+
+"
+    foreach hdr $pkg_inc_files {
+	puts $makefile "\$(PREFIX)/include/$pkg_incdir/$hdr : $hdr"
+	if { $pkgconf::config_data(use_links) == 0 } {
+	    puts $makefile "\t@\$(CP) \$< \$@"
+	} else {
+	    puts $makefile "\t@\$(RM) \$@; ln -s \$< \$@"
+	}
+	puts $makefile ""
+    }
+    
+    puts $makefile "clean:"
+    foreach hdr $pkg_inc_files {
+	puts $makefile "\t@\$(RM) \$(PREFIX)/include/$pkg_incdir/$hdr"
+    }
+    puts $makefile "\n# End of $filename"
+    close $makefile
+}
+
+# ----------------------------------------------------------------------------
+# Processing the src directory is relatively straightforward. It is
+# necessary to create the src directory in the build tree, plus any
+# subdirectories. It is also necessary to copy across the PKGconf.mak
+# file from the component repository to the build tree, assuming the
+# destination file does not already exist, and do the same in any
+# subdirectories.
+#
+proc pkgconf::process_package_src { dir } {
+
+    set srcdir  [file join $pkgconf::component_repository $dir]
+    set destdir [file join $pkgconf::build_tree $dir]
+    
+    file mkdir $destdir
+    set subdirs [locate_all_subdirs $srcdir]
+    foreach subdir $subdirs {
+	if { [is_empty_directory [file join $srcdir $subdir] ] } {
+	    continue
+	}
+	file mkdir [file join $destdir $subdir]
+    }
+
+    set makefiles [locate_all_files $srcdir "PKGconf.mak"]
+    foreach makefile $makefiles {
+	set destname [file join $destdir [file dirname $makefile] "makefile"]
+
+	if { ([file isfile $destname] == 0) || $pkgconf::force_arg } {
+	    file copy -force -- [file join $srcdir $makefile] $destname
+	    make_writable $destname
+	}
+    }
+}
+
+# ----------------------------------------------------------------------------
+# Processing the tests directories is just the same as the source
+# directories, for now. There is one additional complication in that
+# the test executables have to get installed, so it is necessary to
+# keep track of which directories are needed in the install tree.
+
+proc pkgconf::process_package_tests { name dir } {
+    
+    set srcdir  [file join $pkgconf::component_repository $dir]
+    set destdir [file join $pkgconf::build_tree $dir]
+
+    lappend pkgconf::install_test_dirs [get_build_alias $name]
+    
+    file mkdir $destdir
+    set subdirs [locate_all_subdirs $srcdir]
+
+    foreach subdir $subdirs {
+	lappend pkgconf::install_test_dirs [file join [get_build_alias $name] $subdir]
+	file mkdir [file join $destdir $subdir]
+    }
+
+    set makefiles [locate_all_files $srcdir "PKGconf.mak"]
+    foreach makefile $makefiles {
+	set destname [file join $destdir [file dirname $makefile] "makefile"]
+	if { ([file isfile $destname] == 0) || $pkgconf::force_arg } {
+	    file copy -force -- [file join $srcdir $makefile] $destname
+	    make_writable $destname
+	}
+    }
+}
+
+# }}}
+# {{{  Install tree                     
+
+# ----------------------------------------------------------------------------
+# These routines take care of the install tree. The routine
+# produce_install_tree gets invoked after details of all the packages
+# are known, and hence after details of all the subdirectories
+# are known.
+#
+# Currently this routine will empty out the lib and tests directory
+# completely. It will also get rid of any files and subdirectories in
+# include which are no longer of interest.
+#
+# Clearing out libtarget.a and any similar libraries avoids problems
+# if the target architecture has changed. Such a change should cause
+# all source files to be rebuilt via a dependency on the makefile
+# auxiliary files. However it would result in an attempt to create
+# a library archive containing a mixture of architectures, which could
+# cause all sorts of problems. To make sure that the library gets
+# regenerated during the next build all the stamp files have to be
+# deleted.
+#
+# It is not a good idea to manipulate header files unnecessarily,
+# since changing these  would result in application code being
+# recompiled as well as system code. However if the old and new
+# configurations involve a header file with the same name but coming
+# from different packages, (e.g. a standard HAL header if you switch
+# targets or platforms, or a package header if you change the version
+# of that package) then there are complications.
+#
+# For now the code is draconian and just deletes everything. This
+# needs to be optimised.
+#
+
+proc pkgconf::produce_install_tree { } {
+
+    ASSERT { $pkgconf::build_tree != "" }
+
+    if { $pkgconf::config_data(prefix) != "" } {
+	set prefix $pkgconf::config_data(prefix)
+    } else {
+	set prefix [file join $pkgconf::build_tree "install"]
+    }
+    ASSERT { $prefix != "" }
+
+    # There should be at least an include/pkgconf subdirectory
+    ASSERT { $pkgconf::install_inc_dirs != "" }
+    
+    # In Tcl the mkdir call is a no-op if the directory already exists.
+    report "Generating install directory $prefix"
+    file mkdir $prefix
+    file mkdir [file join $prefix "include"]
+    file mkdir [file join $prefix "lib"]
+    file mkdir [file join $prefix "tests"]
+
+    foreach subdir $pkgconf::install_inc_dirs {
+	file mkdir [file join $prefix "include" $subdir]
+    }
+    foreach subdir $pkgconf::install_test_dirs {
+	file mkdir [file join $prefix "tests" $subdir]
+    }
+
+    if { 0 } {
+	
+	# BLV - this code does not work with the makefiles that are now
+	# generated for the various include directories. If the old
+	# configuration involved a header file xyz.h from package A and
+	# the new configuration involves a header file xyz.h from a
+	# different package (or a different version of the same package)
+	# then the old header file would hang around. A temporary
+	# work-around is to delete all header files. A proper fix is
+	# rather more complicated.
+	
+	# Examine all the files currently in the include directory. If
+	# any of them are not recognised then they should be deleted.
+	# Typically this happens if a package is removed.
+	#
+	# There may be problems if users put the install tree under
+	# source code control, in that files just disappear. However even if
+	# the source code control system restores an old file this should be
+	# harmless. The directory routines ignore CVS subdirectories and all
+	# hidden files.
+	#
+	# Files in pkgconf have to be treated specially. These are listed
+	# in pkgconf_files, not header_files.
+    
+	set existing_headers [locate_all_files [file join $prefix "include"]]
+	set pkgconf_headers  [locate_all_files [file join $prefix "include" pkgconf]]
+
+	foreach file $pkgconf_headers {
+	    if { [lsearch -exact $pkgconf::pkgconf_files $file] == -1 } {
+		set filename [file join $prefix "include" $file]
+		report "Deleting unnecessary file $filename"
+		file delete -- $filename
+	    }
+
+	    # Make sure that this file does not get processed again in the
+	    # next loop.
+	    set file  [file join "pkgconf" $file]
+	    set index [lsearch -exact $existing_headers $file]
+	    ASSERT { $index != -1 }
+	    set existing_headers [lreplace $existing_headers $index $index]
+	}
+    
+	foreach file $existing_headers {
+	    if { [lsearch -exact $pkgconf::install_inc_files $file] == -1 } {
+		set filename  [file join $prefix "include" $file]
+		report "Deleting unnecessary file $filename"
+		file delete -- $filename
+	    }
+	}
+
+	# If the user has switched from symbolic links to file copies then
+	# it is also necessary to delete the old symbolic links. The reverse
+	# also applies.
+	set existing_headers [locate_all_files [file join $prefix "include"]]
+	set files_removed 0
+	foreach file $existing_headers {
+	
+	    set filename [file join $prefix "include" $file]
+	    set type [file type $filename]
+	    
+	    if { ($pkgconf::config_data(use_links) == 0 ) && ($type == "link") } {
+		file delete -- $filename
+		set files_removed 1
+	    } elseif { ($pkgconf::config_data(use_links) == 1 ) && ($type == "file") } {
+		file delete -- $filename
+		set files_removed 1
+	    }
+	}
+	if { $files_removed != 0 } {
+	    if { $pkgconf::config_data(use_links) == 0 } {
+		report "Symbolic links in [file join $prefix include] deleted."
+	    } else {
+		report "Header file copies in [file join $prefix include] deleted."
+	    }
+	}
+    
+	# Now get rid of any unwanted subdirectories.
+	set existing_subdirs [locate_all_subdirs [file join $prefix "include"]]
+	foreach subdir $existing_subdirs {
+	    if { [lsearch -exact $pkgconf::install_inc_dirs $subdir] == -1 } {
+		set dirname [file join $prefix "include" $subdir]
+		report "Deleting unnecessary directory $dirname"
+		file delete -force -- $dirname
+	    }
+	}
+	
+    } else {
+
+	# BLV - delete all header files for now. This is inefficient but safe.
+	
+	set existing_headers [locate_all_files [file join $prefix "include"]]
+	foreach file $existing_headers {
+	    set filename [file join $prefix "include" $file]
+	    file delete -- $filename
+	}
+	
+	# Now get rid of any unwanted subdirectories.
+	set existing_subdirs [locate_all_subdirs [file join $prefix "include"]]
+	foreach subdir $existing_subdirs {
+	    if { [lsearch -exact $pkgconf::install_inc_dirs $subdir] == -1 } {
+		set dirname [file join $prefix "include" $subdir]
+		report "Deleting unnecessary directory $dirname"
+		file delete -force -- $dirname
+	    }
+	}
+    }
+
+    # Get rid of all the files in lib.
+    set lib_files [locate_files [file join $prefix "lib"]]
+    if { $lib_files != "" } {
+	report "Deleting old library files $lib_files"
+	foreach file $lib_files {
+	    file delete -- [file join $prefix "lib" $file]
+	}
+    }
+
+    # And get rid of any stamp files spread around the object tree
+    foreach src_dir $pkgconf::src_dirs {
+	set dirname [file join $pkgconf::build_tree $src_dir]
+	set stamp_files [locate_files $dirname "*.stamp"]
+	foreach file $stamp_files {
+	    file delete -- [file join $dirname $file]
+	}
+    }
+    
+    # Ditto for tests
+    set test_files [locate_all_files [file join $prefix "tests"]]
+    if { $test_files != "" } {
+	report "Deleting old test executables."
+	foreach file $test_files {
+	    file delete -- [file join $prefix "tests" $file]
+	}
+    }
+    foreach test_dir $pkgconf::tests_dirs {
+	set dirname [file join $pkgconf::build_tree $test_dir]
+	set stamp_files [locate_files $dirname "*.stamp"]
+	foreach file $stamp_files {
+	    file delete -- [file join $dirname $file]
+	}
+    }
+
+    # If there has been a change in the packages in the system
+    # then the various makefile.deps files currently in the
+    # build tree may contain references to header files that
+    # are no longer present. Therefore it is necessary to
+    # get rid of the makefile.deps files. Any changes to the
+    # packages will result in a new pkgconf/pkgconf.mak file
+    # and all object files depend on this, so everything that
+    # needs to be rebuilt will still be rebuilt and new
+    # dependency files will be generated.
+    #
+    # If there has not been a change to the packages then the
+    # makefile.deps files must not be deleted, or there would
+    # be problems when just a header file is changed.
+    if { [packages_have_changed] } {
+
+	foreach src_dir $pkgconf::src_dirs {
+	    set dirname [file join $pkgconf::build_tree $src_dir]
+	    set dep_files [locate_files $dirname "makefile.deps"]
+	    foreach file $dep_files {
+		file delete -- [file join $dirname $file]
+	    }
+	}
+    
+	foreach test_dir $pkgconf::tests_dirs {
+	    set dirname [file join $pkgconf::build_tree $test_dir]
+	    set dep_files [locate_files $dirname "makefile.deps"]
+	    foreach file $dep_files {
+		file delete -- [file join $dirname $file]
+	    }
+	}
+    }
+}
+
+# }}}
+# {{{  Produce misc. files              
+
+# ----------------------------------------------------------------------------
+# Three files have to be generated, and one additional file can be copied.
+#
+# 1) pkgconf/makevars has to be copied from the component repository.
+# 2) pkgconf/makevars relies on pkgconf/pkgconf.mak, which is generated
+# 3) pkgconf/system.h has to be generated.
+# 5) pkgconf/makefile has to be generated.
+#
+
+proc pkgconf::produce_misc_files { } {
+
+    report "Generating miscellaneous files."
+
+    # Start by copying across pkgconf/makevars unless these files
+    # already exist in the build tree. Note that these files are
+    # user-editable so they cannot be overwritten automatically.
+    set destname   [file join $pkgconf::build_tree "pkgconf" "makevars"]
+    set sourcename [file join $pkgconf::component_repository "pkgconf" "makevars"]
+    if { (![file isfile $destname]) || ($pkgconf::force_arg != 0) } {
+	file copy -force -- $sourcename $destname
+	make_writable $destname
+    }
+
+    # Next generate the file pkgconf.mak. This should only happen if any
+    # of the data in the file is changed, because there are file-level
+    # dependencies in the various makefiles. Additions to the packages
+    # file will normally be caught by a missing version number. Other
+    # changes to the packages or targets file may not be caught.
+
+    set config_changed 0
+    if { [packages_have_changed] } {
+	set config_changed 1
+    }
+    if { [value_has_changed "prefix"] } {
+	set config_changed 1
+    }
+    foreach flag $pkgconf::known_compiler_flags {
+	if { [value_has_changed "cflags,$flag"] } {
+	    set config_changed 1
+	    break
+	}
+    }
+    if { ![file isfile [file join $pkgconf::build_tree "pkgconf" "pkgconf.mak"]] } {
+	set config_changed 1
+    }
+    if { $pkgconf::force_arg != 0 } {
+	set config_changed 1
+    }
+    
+    if { $config_changed != 0 } {
+
+	set filename [file join "pkgconf" "pkgconf.mak"]
+	set file [open [file join $pkgconf::build_tree $filename] "w"]
+	output_banner $file $filename
+
+	# All PKGconf.mak files should include the master pkgconf.mak
+	# file ASAP. It is desirable to have a goal at the top of that
+	# file. This is likely to be the first goal detected by make,
+	# and hence the default.
+	puts $file "\ndefault: build\n"
+	
+	set command_prefix $pkgconf::target_data($pkgconf::config_data(target),prefix)
+
+	puts $file ""
+	puts $file "COMPONENT_REPOSITORY\t\t:= [get_pathname_for_make $pkgconf::component_repository]"
+	puts $file "BUILD_TREE\t\t\t:= [get_pathname_for_make $pkgconf::build_tree]"
+
+	# The prefix (i.e. install directory) should default to somewhere
+	# relative to the build tree.
+	set prefix $pkgconf::config_data(prefix)
+	if { $prefix == "" } {
+	    set prefix [file join $pkgconf::build_tree "install"]
+	}
+	puts $file "PREFIX\t\t\t\t:= [get_pathname_for_make $prefix]"
+	if { $pkgconf::windows_host != 0 } {
+	    puts $file "EXEEXT\t\t\t\t:= .exe"
+	}
+	puts $file ""
+	puts $file "PKGCONF_CC\t\t\t:= $command_prefix-gcc"
+	puts $file "PKGCONF_CXX\t\t\t:= $command_prefix-gcc"
+	puts $file "PKGCONF_AR\t\t\t:= $command_prefix-ar"
+	puts $file "PKGCONF_LD\t\t\t:= $command_prefix-ld"
+	puts $file "PKGCONF_OBJCOPY\t\t\t:= $command_prefix-objcopy"
+	puts $file ""
+
+	foreach flag $pkgconf::known_compiler_flags {
+	    set value ""
+	    if { [info exists pkgconf::config_data(cflags,$flag)] != 0 } {
+		set value $pkgconf::config_data(cflags,$flag)
+	    } else {
+		set value [get_platform_cflags $pkgconf::config_data(target) $pkgconf::config_data(platform) $flag]
+	    }
+	    if { $value != "" } {
+		puts $file "PKGCONF_[set flag]\t\t:= $value"
+	    }
+	}
+	puts $file ""
+
+	foreach pkg [get_current_packages] {
+	    set varname "[get_build_alias $pkg]_DIR"
+	    set dirname [file join $pkgconf::package_data($pkg,dir) $pkgconf::config_data($pkg,version)]
+	    puts $file "$varname\t\t:= $dirname"
+	}
+	
+	# There are several files containing information about make variables
+	# rules. The current file, pkgconf.mak, contains information that
+	# is known to the pkgconf script and should not be edited by users.
+	# There may be a per-package file in the build tree's pkgconf
+	# directory, containing per-package settings. And there is the
+	# makevars file which users can edit to override anything else.
+	#
+	# The makefiles for the various packages should only
+	# need to include one of these files, the rest can be handled
+	# automatically. Since pkgconf.mak is generated it knows the
+	# location of the build tree, making it a lot easier to find
+	# the others. Therefore pkgconf.mak is included first, then
+	# the per-package file, and finally the makevars file.
+
+	puts $file "
+PACKAGE_RULES_FILE := \$(wildcard \$(BUILD_TREE)/pkgconf/\$(PACKAGE).mak)
+ifneq ($(PACKAGE_RULES_FILE),)
+include $(PACKAGE_RULES_FILE)
+endif"
+
+	puts $file "\ninclude \$(BUILD_TREE)/pkgconf/makevars"
+
+	puts $file "\n# End of $filename"
+	close $file
+    }
+    
+    # And the file system.h
+    #
+    # Again, because of file-level dependencies this file should only
+    # be written out if anything has actually changed.
+    set config_changed 0
+    if { [packages_have_changed] || [value_has_changed "startup"] } {
+	set config_changed 1
+    }
+    if { ![file isfile [file join $pkgconf::build_tree "pkgconf" "system.h"]] } {
+	set config_changed 1
+    }
+    if { $pkgconf::force_arg != 0 } {
+	set config_changed 1
+    }
+
+    if { $config_changed != 0 } {
+
+	set filename [file join "pkgconf" "system.h"]
+	set file [open [file join $pkgconf::build_tree $filename] "w"]
+	puts $file \
+"#ifndef CYGONCE_PKGCONF_SYSTEM_H
+#define CYGONCE_PKGCONF_SYSTEM_H
+/*
+ * File $filename
+ *
+ * This file is generated automatically by the pkgconf program.
+ * It should not be edited. Any changes to this file may be
+ * overwritten by pkgconf.
+ */
+"
+        puts $file "#define CYG_HAL_[string toupper $pkgconf::config_data(target)]"
+        puts $file \
+	    "#define CYG_HAL_[string toupper $pkgconf::config_data(target)]_[string toupper $pkgconf::config_data(platform)]"
+        puts $file "#define CYG_HAL_STARTUP_[string toupper $pkgconf::config_data(startup)]"
+        puts $file ""
+        set current_packages [get_current_packages]
+        foreach pkg $pkgconf::known_packages {
+	    if { [lsearch -exact $current_packages $pkg] == -1 } {
+		puts $file "#undef  $pkg"
+	    } else {
+		puts $file "#define $pkg"
+	    }
+	}
+
+	puts $file "\
+\n#endif  /* CYGONCE_PKGCONF_SYSTEM_H */ 		
+/* EOF $filename */"
+	close $file
+    }
+
+    # Always output a makefile in the pkgconf directory. This is harmless
+    # because there should be no file-level dependencies on this file (just
+    # like the toplevel makefile).
+    set filename [file join "pkgconf" "makefile"]
+    set makefile [open [file join $pkgconf::build_tree $filename] "w"]
+    output_banner $makefile $filename
+
+    puts $makefile \
+"include pkgconf.mak
+
+.PHONY  : build clean
+HEADERS := $pkgconf::pkgconf_files
+TARGETS := \$(foreach hdr,\$(HEADERS),\$(PREFIX)/include/pkgconf/\$(hdr))
+
+build: \$(TARGETS)
+"
+
+    foreach hdr $pkgconf::pkgconf_files {
+	puts $makefile "\$(PREFIX)/include/pkgconf/$hdr : $hdr"
+	if { $pkgconf::config_data(use_links) == 0 } {
+	    puts $makefile "\t@\$(CP) \$< \$@"
+	} else {
+	    puts $makefile "\t@\$(RM) \$@; ln -s \$< \$@"
+	}
+	puts $makefile ""
+    }
+
+    puts $makefile "clean:"
+    foreach hdr $pkgconf::pkgconf_files {
+	puts $makefile "\t@\$(RM) \$(PREFIX)/include/pkgconf/$hdr"
+    }
+    puts $makefile "\n# End of $filename"
+    close $makefile
+    
+}
+
+# }}}
+# {{{  Produce the master makefile      
+
+# ----------------------------------------------------------------------------
+# This routine is responsible for generating the master makefile that
+# exists at the top of a build tree. This makefile supports three main
+# targets: default, tests and clean. There is a subsidiary target
+# "headers" which takes care of the header files.
+#
+
+proc pkgconf::produce_makefile { } {
+
+    report "Generating toplevel makefile."
+    
+    ASSERT { $pkgconf::build_tree != "" }
+    set filename [file join $pkgconf::build_tree "makefile"]
+    set makefile [open $filename "w"]
+    output_banner $makefile "makefile"
+
+    puts $makefile \
+"
+include pkgconf/pkgconf.mak
+.PHONY: default build clean tests headers"
+
+    puts $makefile "\nbuild: headers"
+    foreach src_dir $pkgconf::src_dirs {
+	puts $makefile "\t\$(MAKE) -C $src_dir"
+    }
+    puts $makefile "\t@echo Build finished."
+    
+    puts $makefile "\ntests: build"
+    foreach tests_dir $pkgconf::tests_dirs {
+	puts $makefile "\t\$(MAKE) -C $tests_dir"
+    }
+    puts $makefile "\t@echo Tests build finished."
+    
+    puts $makefile \
+"\nlibobjs := \$(wildcard \$(PREFIX)/lib/*)
+clean: 
+ifneq (\$(libobjs),)
+\t\$(RM) \$(libobjs)
+endif
+\tmake -C pkgconf clean"
+    foreach src_dir $pkgconf::src_dirs {
+	puts $makefile "\t\$(MAKE) -C $src_dir clean"
+    }
+    foreach tests_dir $pkgconf::tests_dirs {
+	puts $makefile "\t\$(MAKE) -C $tests_dir clean"
+    }
+    foreach inc_dir $pkgconf::inc_dirs {
+	puts $makefile "\t@\$(MAKE) -silent -C $inc_dir clean"
+    }
+    puts $makefile "\t@echo Clean finished."
+    
+    puts $makefile "\nheaders:"
+    puts $makefile "\t@echo Checking header files."
+    puts $makefile "\t@\$(MAKE) -silent -C pkgconf"
+    foreach inc_dir $pkgconf::inc_dirs {
+	puts $makefile "\t@\$(MAKE) -silent -C $inc_dir"
+    }
+    
+    puts $makefile ""
+    puts $makefile "# End of makefile"
+    close $makefile
+}
+
+# }}}
+
+# {{{  Produce_build_tree               
+
+proc pkgconf::produce_build_tree { } {
+
+    # First, check that all the requirements have been satisfied. Normally
+    # this will have been taken care of earlier.
+    check_requirements
+
+    # What is the install tree ?
+    set prefix $pkgconf::config_data(prefix)
+    if { $prefix == "" } {
+	set prefix [file join $pkgconf::build_tree "install"]
+    }
+    set target   $pkgconf::config_data(target)
+    set platform $pkgconf::config_data(platform)
+    set startup  $pkgconf::config_data(startup)
+    
+    # Report the configuration that is being built.
+    report "Generating build tree."
+    report "Component repository is $pkgconf::component_repository"
+    report "Build tree is $pkgconf::build_tree"
+    report "Install directory is $prefix"
+    report "Architecture $target, platform $platform, startup $startup."
+    if { $pkgconf::config_data(use_links) != 0 } {
+	report "Using symbolic links for the header files."
+    }
+    
+    # Reset the list of all the header files that should be in the install
+    # tree, and related variables.
+    set pkgconf::inc_dirs               ""
+    set pkgconf::src_dirs               ""
+    set pkgconf::tests_dirs             ""
+    set pkgconf::install_inc_dirs       "pkgconf"
+    set pkgconf::install_inc_files      ""
+    set pkgconf::pkgconf_files          "makevars pkgconf.mak system.h"
+    set pkgconf::install_test_dirs      ""
+    
+    set status [catch {
+
+	# Create the toplevel build tree directory, if necessary.
+	if { ! [file exists $pkgconf::build_tree] } {
+	    report "Creating build tree $pkgconf::build_tree"
+	    file mkdir $pkgconf::build_tree
+	}
+	# Create the pkgconf subdirectory. A failure here indicates that
+	# the build tree is not writable, and it is useful to detect this
+	# before too much processing takes place.
+	set dirname [file join $pkgconf::build_tree pkgconf]
+	if { ! [file exists $dirname] } {
+	    report "Creating directory $dirname"
+	    file mkdir $dirname
+	}
+
+	# Process each package that is part of the current configuration
+	foreach pkg [get_current_packages] {
+	    set dirname $pkgconf::package_data($pkg,dir)
+	    set dirname [file join $dirname $pkgconf::config_data($pkg,version)]
+	    process_package $pkg $dirname
+	}
+
+	# The install tree must exist before the misc files are generated.
+	# See get_pathname_for_make for details.
+	produce_install_tree
+	produce_misc_files
+	produce_makefile
+	
+	report "Generating save file."
+	write_save_file
+	report "The Build tree is ready."
+	
+    } message]
+
+    if { $status != 0 } {
+	fatal_error "An unexpected error occurred while generating the build tree: $message"
+    }
+}
+
+# }}}
+
+# }}}
+# {{{  main()                           
+
+# ----------------------------------------------------------------------------
+# Initialise data. This routine is responsible for reading in the
+# packages file and the targets file, setting up the default data,
+# reading in the save file if any, and processing the command line
+# arguments if any.
+#
+
+proc pkgconf::initialise_data { } {
+    
+    # First, read the packages and targets files. These routines will
+    # also deal with the --packages and --targets arguments, and
+    # they will check for any invalid packages or targets that may
+    # have been passed. The order of these two calls should not be
+    # changed since read_targets needs to know about the available
+    # packages for some of its validations.
+    pkgconf::read_packages
+    pkgconf::read_targets
+
+    # If either or both of the --packages or --targets arguments
+    # were used, do not go any further. The user was just asking
+    # for information. Ditto for --pkgdata, which can be used by
+    # other programs to get information about the various
+    # packages in a format more appropriate for them.
+    #
+    # NOTE: the parsing code does not actually check that no
+    # further arguments were passed, but it is unlikely that the
+    # user would both ask for a list of packages and expect
+    # something to happen.
+    if { ( $pkgconf::list_packages_arg != 0 ) ||
+         ( $pkgconf::list_targets_arg != 0 )  ||
+         ( $pkgconf::list_pkgdata_arg != 0 ) } {
+	exit 0
+    }
+
+    # Set up default values for all options which are managed directly
+    # by this program.
+    pkgconf::setup_defaults
+    
+    # Check for a save file in the existing build tree.
+    # If so read it in, updating the default values.
+    pkgconf::read_save_file
+
+    # Now process the command line arguments, which will again result
+    # in updates to the default values.
+    pkgconf::process_arguments
+
+    # If the -flags argument was used, list the compiler flags currently
+    # in use. This could not be done earlier since the compiler flags
+    # may depend on the save file and on other command line arguments.
+    if { $pkgconf::list_flags_arg != 0 } {
+	foreach flag $pkgconf::known_compiler_flags {
+	    set value ""
+	    if { [info exists pkgconf::config_data(cflags,$flag)] != 0 } {
+		set value $pkgconf::config_data(cflags,$flag)
+	    } else {
+		set value [get_platform_cflags $pkgconf::config_data(target) $pkgconf::config_data(platform) $flag]
+	    }
+	    if { $value != "" } {
+		puts "$flag\t\"$value\""
+	    }
+	}
+	exit 0
+    }
+    
+    if { 0 } {
+	puts "target:   $pkgconf::config_data(target)"
+	puts "platform: $pkgconf::config_data(platform)"
+	puts "startup:  $pkgconf::config_data(startup)"
+	puts "prefix:   $pkgconf::config_data(prefix)"
+	puts "packages: $pkgconf::config_data(packages)"
+	foreach pkg $pkgconf::config_data(packages) {
+	    puts "$pkg version $pkgconf::config_data($pkg,version)"
+	}
+	exit 0
+    }
+}
+
+# ----------------------------------------------------------------------------
+# Main(). This code only runs if the script is being run stand-alone rather
+# than as part of a larger application. The controlling predicate is the
+# existence of the variable pkgconf_not_standalone which can be set by
+# the containing program if any.
+#
+
+if { ! [info exists pkgconf_not_standalone] } {
+
+    # Do some necessary GUI initialisation such as hiding the main window
+    # during startup.
+    pkgconf::gui_standalone_init
+
+    # Decide where warnings and fatal errors should go.
+    pkgconf::initialise_error_handling
+    
+    # Parse the arguments and set the global variables appropriately.
+    pkgconf::parse_arguments $argv0 $argv
+
+    # Initialise all the data, e.g. by reading in the packages and
+    # targets files.
+    pkgconf::initialise_data
+    
+    # At this stage the code can do one of two things: run the GUI,
+    # or build/update the build tree.
+    # The GUI can be run if the necessary commands are available and
+    # if the user did not pass -nw.
+    if { $pkgconf::gui_possible && ! $pkgconf::no_windows_arg } {
+    } else {
+	set pkgconf::gui_mode 0
+    }
+
+    # Reset error handling to the correct mode
+    pkgconf::initialise_error_handling
+
+    # Either enter GUI mode, or produce the build tree
+    if { $pkgconf::gui_mode } {
+	pkgconf::gui
+    } else {
+	pkgconf::produce_build_tree
+	exit 0
+    }
+}
+
+# }}}
+
+