Skip to content
Snippets Groups Projects
Forked from Core Modules / dune-common
5436 commits behind the upstream repository.
dunemodules.lib 15.31 KiB
# -*-sh-*-
# vim: ft=sh

###############################################
###
### Configuration
###

# name of the "control" files
CONTROL="dune.module"

###############################################
###
### check for environment variables
###

if test -z $GREP; then
  GREP=grep
fi
# SunOS [e]grep does not seem to comprehend character classes.  Set up
# some variables to spell them out
UPPER=ABCDEFGHIJKLMNOPQRSTUVWXYZ
LOWER=abcdefghijklmnopqrstuvwxyz
ALPHA="$UPPER$LOWER"
DIGIT=0123456789
ALNUM="$ALPHA$DIGIT"

space=" "
formfeed=""
newline="
"
cr="
"
tab="	"
vtab=""
# $SPACE will unfortunately not work since grep will not accept an
# embedded newline.  Instead one can often get away with using $BLANK
SPACE="$space$formfeed$newline$cr$tab$vtab"
BLANK="$space$tab"

#
# read paramters from a $CONTROL file
#
# paramters:
# $1 file to read
#
PARSER_TRIM="awk '{gsub(/^[[:space:]]+| +$/,\"\");printf(\"%s\", \$0);}'"
parse_control() {
  # check file existence
  if test ! -f "$1" -o "$(basename $1)" != "$CONTROL"; then
    echo "ERROR: '$1' is no $CONTROL file" >&2
    exit 1
  fi
  # reset information handling
  module=""
  module_inst="no"
  # read parameters from control file
  local name="$($GREP Module: "$1" | cut -d ':' -f2 | eval $PARSER_TRIM)"
  if test "x$name" = "x"; then
    echo "ERROR: $CONTROL files $1 does not contain a Module entry" >&2
    exit 1
  fi
  # create and check variable name from module name
  export module=$(fix_variable_name $name)
  if ! check_modname "$module"; then
    echo "ERROR: $CONTROL files $1 contains an invalid Module entry" >&2
    exit 1
  fi
  # read dune.module file
  local deps="$($GREP "^[$BLANK]*Depends:" "$1" | cut -d ':' -f2 | eval $PARSER_TRIM)"
  local sugs="$($GREP "^[$BLANK]*Suggests:" "$1" | cut -d ':' -f2 | eval $PARSER_TRIM)"
  local vers="$($GREP "^[$BLANK]*Version:" "$1" | cut -d ':' -f2 | eval $PARSER_TRIM)"
  local main="$($GREP "^[$BLANK]*Maintainer:" "$1" | cut -d ':' -f2 | eval $PARSER_TRIM)"
  # check whether the module is installed.
  # - installed modules can be found via pkg-config
  # - pkg-config --var=prefix should be the same as $path
  #
  # the path contains a different sub structure
  # for installed and source modules
  # - installed module: ${path}/lib/dunecontrol/${name}/dune.module
  #   and there is a file ${path}/lib/pkgconfig/${name}.pc
  # - source module: ${path}/dune.module
  #   and there is a file ${path}/${name}.pc.in
  local path="$(canonicalpath "$1")"
  if pkg-config $name; then
    local prefix="$(pkg-config --variable=prefix $name)"
    local pkgpath=$(canonicalname "$prefix/lib/dunecontrol/$name")
    if test x"$pkgpath" = x"$path"; then
	  path="$prefix"
      module_inst="yes"
    fi
  fi
  # avoid multiple definition of the same module
  if eval test "x\$HAVE_$module" != "x"; then
    # make sure we don't stumble over the same module twice
    if eval test "\$PATH_$module" = "$path"; then
      return
    fi
    local old_mod_inst
    eval old_mod_inst=\$INST_$module
    case "$old_mod_inst$module_inst" in
    # multiple local modules are an error
    # multiple installed modules are an error
    nono|yesyes)
      echo "ERROR: multiple definition of module $name" >&2
      echo "previous defined in:" >&2
      if eval test x\$INST_$module = "xyes"; then
        echo "  $(eval echo \$PATH_$module)/lib/dunecontrol/$name/$CONTROL" >&2
      else
        echo "  $(eval echo \$PATH_$module)/$CONTROL" >&2
      fi
      echo "redefined in:" >&2
      if test "$module_inst" = "yes"; then
        echo "  $path/lib/dunecontrol/$name/$CONTROL" >&2
      else
        echo "  $path/$CONTROL" >&2
      fi
      exit 1
    ;;
    # installed modules are superseded by locally built modules
    noyes)
      echo "WARNING: ignoring installed module file" >&2
      echo "  $path/lib/dunecontrol/$name/$CONTROL" >&2
      echo "using previously found locally built module" >&2
      echo "  $(eval echo \$PATH_$module)/$CONTROL" >&2
	  module_inst="no"
      return
    ;;
    # local modules supersede installed modules
    yesno)
      echo "WARNING: not using installed module file" >&2
      echo "  $(eval echo \$PATH_$module)/lib/dunecontrol/$name/$CONTROL" >&2
      echo "using locally built module" >&2
      echo "  $path/$CONTROL" >&2
      true # do nothing, ignore the previously found module
    ;;
    esac
  fi
  # set status variables
  export HAVE_$module=yes
  export PATH_$module="$path"
  export VERS_$module="$vers"
  export NAME_$module="$name"
  export MAIN_$module="$main"
  export DEPS_$module="$deps"
  export INST_$module="$module_inst"
  for name in $deps; do
    mod=$(fix_variable_name $name)
    export NAME_$mod="$name"
  done
  export SUGS_$module="$sugs"
  for name in $sugs; do
    mod=$(fix_variable_name $name)
    export NAME_$mod="$name"
  done
  # update list of modules
  if test "$module_inst" = "yes"; then
    export INSTMODULES="$INSTMODULES$module "
  else
    export LOCALMODULES="$LOCALMODULES$module "
  fi
}

#
# try to setup the control path
#
setup_control_path() {
  if test -z $DUNE_CONTROL_PATH; then
    DUNE_CONTROL_PATH=.
    # try pkg-config locations
    if pkg-config dune-common; then
      # try usual locations of installed modules
      for i in /usr/local/lib/dunecontrol/ /usr/lib/dunecontrol/; do
        if test -d $i; then
          DUNE_CONTROL_PATH=$DUNE_CONTROL_PATH:"$i"
        fi
      done
      for i in `echo $PKG_CONFIG_PATH | tr ':' ' '`; do
        if test -d "$i/../dunecontrol"; then 
          DUNE_CONTROL_PATH=$DUNE_CONTROL_PATH:"$i/../dunecontrol"
        fi
      done
    fi
  fi
  # try to read DUNE_CONTROL_PATH from OPTS file
  if test -n "$DUNE_OPTS_FILE"; then
    DUNE_CONTROL_PATH="$(. $DUNE_OPTS_FILE; eval echo $DUNE_CONTROL_PATH)"
  fi
  # canonicalize path
  local TMP=""
  # foreach dir in $@
  while read dir; do
    TMP=$TMP:"$(canonicalname $dir)"
  done <<EOF
    $(echo $DUNE_CONTROL_PATH | sed -e 's/:\+/:/g' | tr ':' '\n')
EOF
  # sort+uniq path
  DUNE_CONTROL_PATH="$(echo $TMP | tr ':' '\n' | sort -u | tr '\n' ':' | sed -e 's/^://' -e 's/:$//')"
  # safe result
  export DUNE_CONTROL_PATH
}

#
# search for modules in each directory in DUNE_CONTROL_PATH
#
find_modules_in_path() {
  setup_control_path
  if test -z "$FOUND_MODULES"; then
    # foreach dir in $@
    while read dir; do
      if test -d "$dir"; then
        while read m; do
          test -n "$m" && parse_control "$m"
        done <<EOFM
        $(find -H "$dir" -name $CONTROL | $GREP -v 'dune-[-_a-zA-Z]/dune-[-a-zA-Z_]*-[0-9]\{1,\}.[0-9]\{1,\}/')
EOFM
	  else
        parse_control "$dir"
	  fi
    done <<EOF
    $(echo $DUNE_CONTROL_PATH | sed -e 's/:\+/:/g' | tr ':' '\n') 
EOF
    export MODULES="$LOCALMODULES$INSTMODULES"
    export FOUND_MODULES="$MODULES"
  else
    export MODULES="$FOUND_MODULES"
  fi
}

#
# sort $MODULES according to the dependencies
#
sort_modules() {
  # reset lists
  export SORTEDMODULES=""
  export REVERSEMODULES=""
  export SORTEDMODULES_SUB=""
  # handle each modules passed as parameter
  for m in "$@"; do
    # did we find a module file for this module?
    if eval test x\$HAVE_$m != x; then
      _sort_module $m MAIN
    else
      echo "ERROR: could not find module $(eval echo \$NAME_$m)" >&2
      exit 1
    fi
  done
  # save result
  export MODULES="$SORTEDMODULES"
  # setup list of SUGS/DEPS and the INFO list
  export SORTEDMODULES_INFO=""
  export SORTEDMODULES_DEPS=""
  export SORTEDMODULES_MAIN=""
  export SORTEDMODULES_SUGS=""
  local mode
  for m in $MODULES; do
    eval mode=\$MODE_$m
    SORTEDMODULES_INFO="$SORTEDMODULES_INFO $m[$mode]"
    eval SORTEDMODULES_$mode=\"\$SORTEDMODULES_$mode $m\"
  done
  export SORTEDMODULES_INFO
  export SORTEDMODULES_DEPS
  export SORTEDMODULES_SUGS
  export SORTEDMODULES_MAIN
  # clean up temporary variables
  for m in $MODULES; do
    export MODE_$m=""
    export SORT_DONE_$m=""
    export SORT_DEPS_DONE_$m=""
    export SORT_SUGS_DONE_$m=""
  done
}

_check_deps()
{
  local module="$1"
  local mode="$2"
  local depmode="$3"
  local report="ERROR"
  local requires="requires"
  local required="required"
  local dependency="dependency"
  if test "x$mode" = "xSUGS"; then
    report="WARNING"
    requires="suggests"
    required="suggested"
    dependency="suggestion"
  fi
  eval deps=\$${mode}_$module
  #initially remove leading space
  deps=`echo ${deps//^[, ]}`
  while test -n "$deps"; do
    #the end of the name is marked either by space, opening parenthesis,
    #or comma
    name="${deps%%[ (,]*}"
    #remove the name and adjacent whitespace
    deps=`echo "$deps" | sed 's/^[^ (,]* *//'`
    #check whether there is a dependency version
    case "$deps" in
    '('*) deps="${deps#(}"
          depver="${deps%%)*}"
          deps="${deps#*)}"
          ;;
    *)    depver=
          ;;
    esac
    #remove any leading whitespace or commas for the next iteration
    deps=`echo ${deps//^[, ]}`
    dep=$(fix_variable_name $name)
    if ! check_modname $dep; then
      echo "ERROR: invalid module name $name ($dependency of $module)" >&2
      exit 1
    fi
    if eval test x\$HAVE_$dep != "x"; then
      eval ver=\$VERS_$dep
      if test "$SKIPVERSIONCHECK" != "yes" && ! check_version "$ver" "$depver"; then
        echo "$report: version mismatch." >&2
        echo "       $modname $requires $name $depver," >&2
        echo "       but only $name = $ver is available." >&2
        if test "x$mode" = "xDEPS"; then
          exit 1
        else
          echo "Skipping $name!" >&2
          continue
        fi
      fi
      _sort_module $dep $depmode
    else
      # perhaps this module is installed,
      # then it should be handled via pkg-config
      if ! pkg-config $name; then
        echo "$report: could not find module $name," >&2
        echo "       module is also unknown to pkg-config." >&2
        echo "       Maybe you need to adjust PKG_CONFIG_PATH!" >&2
        echo "       $name is $required by $modname" >&2
        if test "x$mode" = "xDEPS"; then
          exit 1
        else
          echo "Skipping $name!" >&2
          continue
        fi
      else
        eval ver=$(pkg-config $name --modversion)
        if test "$SKIPVERSIONCHECK" != "yes" && ! check_version "$ver" "$depver"; then
          echo "$report: version mismatch." >&2
          echo "       $modname $requires $name $depver," >&2
          echo "       but only $name = $ver is installed." >&2
          if test "x$mode" = "xDEPS"; then
            exit 1
          else
            echo "Skipping $name!" >&2
            continue
          fi
        fi
        # update module list
        parse_control $(pkg-config $name --variable=prefix)/lib/dunecontrol/$name/dune.module
        _sort_module $dep $depmode
      fi
    fi
  done
}

#
# recursive part of sort_modules
# evaluate dependencies of one module
#
# paramters:
# $1 name of the modules
# $2 parser mode:
#    DEPS: search for dependencies
#    SUSG: search for suggestions (DEPS of SUGS are handled as SUGS)
#    MAIN: primary invocation of a DEPS search, 
#          MAIN modules are not added to the list of DEPS/SUGS
#
_sort_module() {
  local module="$1"
  local mode="$2"
  test -n "$mode"
  local modname=""
  eval modname=\$NAME_$module
  local deps=""
  local name=""
  local dep=""
  local ver=""
  local depver=""
  shift 1
  if ! check_modname $module; then 
    echo "ERROR: invalid module name $module" >&2
    exit 1
  fi
  depmode=$(test $mode = SUGS && echo SUGS || echo DEPS)
  if eval test "x\$SORT_${depmode}_DONE_$module" != "xyes"; then
    # stop any recursion
    export SORT_${depmode}_DONE_$module=yes
    # resolve dependencies
    _check_deps $module DEPS $depmode # it might happen that the DEPS are actually SUGS
    # resolve suggestions
    _check_deps $module SUGS SUGS
    # remember mode of the module
    if eval test "x\$MODE_$module" = xSUGS -o  "x\$MODE_$module" = x; then
      export MODE_$module=$mode
    fi
    # topological list of the module and its dependencies/suggestions
    if eval test "x\$SORT_DONE_$module" != "xyes"; then
      export SORT_DONE_$module=yes
      export SORTEDMODULES="$SORTEDMODULES $module"
      export REVERSEMODULES="$module $REVERSEMODULES"
    fi
  fi
}

#
# load the $CONTROL file, skip all control variables
# and run a command
#
# parameters:
# $1 command to execute
# $2 full path of the $CONTROL file
#
eval_control() {
  local command="$1"
  local file="$2"
  shift 2
  if test -f "$file"; then
    # open subshell
    (
      set -e
      # load functions defined in $file
      # if $command is not defined in $file,
      # then the default implementation will be executed
      eval "$($GREP -v "^[-$ALNUM]\{1,\}:" $file)"
      # execute $command
      $command
    ) || false
  else
    echo "ERROR: could not find $file" >&2
    exit 1
  fi
}

#
# fix a value such that it is suitable for a variable name
#
# parameters:
# $1 value
#
fix_variable_name() {
  echo ${@//[[:punct:]]/_}
}

#
# fix a value such that it is suitable for a variable name and assign it
#
# parameters:
# $1 name of variable
# $2 value
#
fix_and_assign() {
  local name="$1"
  if ! check_modname $name; then
    echo "ERROR: error in assignment. $name is not a valid variabel name." >&2
  fi
  shift 1
  export $name=$(fix_variable_name $@)
}

#
# make sure the module name fits the naming convention
# (we try to assign the name and report upon failure)
#
# parameters:
# $1 module name
#
check_modname() {
  # magic pattern match, see http://www.linuxmisc.com/9-unix-questions/67d307ca51f16ed4.htm
  [ -n "${1##*[!A-Za-z0-9_]*}" ] && [ -n "${1##[!A-Za-z_]*}" ]
}

#
# compare a sub part of the version string
#
# parameters:
# $1 version
# $2 part
#
# parts:
# 1: major
# 2: minor
# 3: revision
#
get_sub_version() {
  #it would be nice to give the part to awk via a "-v FIELD=$2"
  #command line argument.  Unfortunatly, SunOS does not support this.
  #Worse, we cannot use awks int() function here, since under SunOS it
  #will always return 0 for string input.
  echo $1 | cut -d. -f"$2" | sed 's/[^0-9].*$//;s/^$/0/'
}

#
# compare two versions
#
# parameters:
# $1 version1
# $2 version1
#
# return:
# 0: equal
# 1: v1 > v2
# 2: v1 < v2
#
compare_versions() {
  local v1="$1"
  local v2="$2"

  for i in 1 2 3; do
    compare_sub_version $v1 $v2 $i || return 0
  done

  echo "eq"
}

compare_sub_version() {
  # compare sub version number
  local sub1=`get_sub_version $1 $3`
  local sub2=`get_sub_version $2 $3`

  if test $sub1 -gt $sub2; then
    echo "gt"
	return 1
  fi
  if test $sub1 -lt $sub2; then
    echo "lt"
	return 1
  fi

  return 0
}

check_version() {
  if test -z "$2"; then # if no constraint is given, check_version is true
    return 0
  fi
  local v=$1
  local PATTERN="^ *\([<>=]*\) *\([0-9.]*\)\(.*\)$"
  if test x != `echo "$2" | sed -e "s/$PATTERN/x/"`; then
    echo "ERROR: invalid version constraint $2" >&2
    exit 1
  fi
  local op=`echo "$2" | sed -e "s/$PATTERN/\1/"`
  local v2=`echo "$2" | sed -e "s/$PATTERN/\2/"`
  local rest=`echo "$2" | sed -e "s/$PATTERN/\3/" -e 's/ //g'`
  local result=1
  
  local rel=`compare_versions $v $v2`

  case $rel$op in
  "eq<="|"eq="|"eq>="|\
  "gt>="|"gt>"|\
  "lt<="|"lt<")
    result=0
	;;
  esac
  if test -z "$rest"; then
    return $result
  fi
  PATTERN="\([|&]\{2\}\)\(.*\)$"
  if test xx != x`echo "$rest" | sed -e "s/$PATTERN/x/"`; then
    echo "ERROR: invalid version constraint '$rest'" >&2
    exit 1
  fi
  op=`echo "$rest" | sed -e "s/$PATTERN/\1/"`
  v2=`echo "$rest" | sed -e "s/$PATTERN/\2/"`
  if eval "test $result -eq 0" $op "check_version \"$v\" \"$v2\""; then
    return 0
  fi
  return 1
}