Mercurial > flash_v2
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 + } +} + +# }}} + +
