Hello...
Seeing that some packages in LFS require pkg-config to build, I wrote a
bash script that implements most of pkg-config's functionality, in order
to avoid setting the flags manually for each package that needs it. I
put it in /tools/bin and did a full build of LFS, with a few packages
from BLFS, without any problems. I also verified that it returns the
same results as the standard implementation for all calls to pkg-config
made during the build, except for differences in error messages and the
order of flags of independent libraries.
I thought some of you might find it useful too. Not sure if and how it
can be used in the book, though.
What do you think?
#!/bin/bash
# version of pkg-config
this_version=0.26
# List of "global" variables, defined for all packages. The list includes
# pre-defined variables such as "pc_top_builddir" (not yet implemented) and
# variables defined by the user with --define-variable.
declare -A global_vars
# Usage: cmp_versions VERSION-1 CMP VERSION-2
#
# CMP must be either "=", "!=", "<", ">", "<=", or ">=". The function returns 0
# if the comparison is true, 1 otherwise.
#
# This is how the standard pkg-config compares two version strings:
# 1. Both strings are split into distinct segments. Each segment contains
# either only digits or only letters. Other characters between segments or
# at the start or the end of the strings are ignored. The segments may be of
# different lengths in the two strings.
# 2. If both strings have no segments left, they are compared equal; stop and
# return this result. If only one of the strings has no segments left, it is
# compared less than the other string; stop and return this result.
# 3. Examine the first segment of each string. If one of the segments contains
# digits and the other segment contains letters, the former is compared
# greater than the latter; stop and return this result.
# 4. Compare the first segment of the strings, either numerically (if both
# contain only digits) or lexicographically (if both contain only letters).
# If they are not compared equal, stop and return the result.
# 5. Move to the next segment of each string and go back to step 2.
function cmp_versions () {
local str1="$1"
local cmp="$2"
local str2="$3"
local first1 first2
local seg1 seg2
# remove non-alphanumeric characters from the beginning
str1="$(expr "$str1" : '[^0-9A-Za-z]*\(.*\)')"
str2="$(expr "$str2" : '[^0-9A-Za-z]*\(.*\)')"
while [ -n "$str1" -a -n "$str2" ]
do
# extract the first segments and check that they have the same
# type
first1=${str1:0:1}
first2=${str2:0:1}
case "$first1$first2" in
[0-9][A-Za-z])
[ "$cmp" = "!=" -o "$cmp" = ">" -o "$cmp" =
">=" ]
return
;;
[A-Za-z][0-9])
[ "$cmp" = "!=" -o "$cmp" = "<" -o "$cmp" =
"<=" ]
return
;;
[0-9][0-9])
seg1="$(expr "$str1" : '\([0-9]*\)')"
seg2="$(expr "$str2" : '\([0-9]*\)')"
;;
[A-Za-z][A-Za-z])
seg1="$(expr "$str1" : '\([A-Za-z]*\)')"
seg2="$(expr "$str2" : '\([A-Za-z]*\)')"
;;
esac
# expr compares numerically or lexicographically
if expr "$seg1" "!=" "$seg2" >/dev/null
then
if expr "$seg1" ">" "$seg2" >/dev/null
then
[ "$cmp" = "!=" -o "$cmp" = ">" -o "$cmp" =
">=" ]
return
else
[ "$cmp" = "!=" -o "$cmp" = "<" -o "$cmp" =
"<=" ]
return
fi
fi
# remove the first segment and non-alphanumeric characters that
# may follow it
str1="${str1#$seg1}"
str2="${str2#$seg2}"
str1="$(expr "$str1" : '[^0-9A-Za-z]*\(.*\)')"
str2="$(expr "$str2" : '[^0-9A-Za-z]*\(.*\)')"
done
# if only one is empty, it is compared less than the other
if [ -n "$str1" ]
then
[ "$cmp" = "!=" -o "$cmp" = ">" -o "$cmp" = ">=" ]
return
fi
if [ -n "$str2" ]
then
[ "$cmp" = "!=" -o "$cmp" = "<" -o "$cmp" = "<=" ]
return
fi
# if both are empty, they are compared equal
[ "$cmp" = "=" -o "$cmp" = "<=" -o "$cmp" = ">=" ]
return
}
pc_path=/usr/lib/pkgconfig:/usr/share/pkgconfig
function pkg_pcfile () {
local pkg="$1"
local dir fullpath
local oldifs
oldifs=$IFS
IFS=:
for dir in $pc_path
do
fullpath="$dir/$pkg.pc"
if [ -r "$fullpath" ]
then
IFS=$oldifs
echo "$fullpath"
return 0
fi
done
IFS=$oldifs
return 1
}
# Usage: substitute_vars STRING
#
# Variable names and values are passed on stdin, one variable per line, in the
# form of "name=value". The function prints the given string with '${VAR}'
# expression substituted with the value of VAR.
function substitute_vars () {
local string="$1"
local -A vars
local line
local varname varvalue
local result
while read line
do
[ -n "$line" ] || continue
varname="${line%%=*}"
varvalue="${line#*=}"
vars[$varname]="$varvalue"
done
while [ -n "$string" ]
do
case "$string" in
\$\$*)
result+='$'
string="${string:2}"
;;
\${*}*)
varname="${string:2}"
varname="${varname%%\}*}"
if [ x${global_vars[$varname]+set} = xset ]
then
result+="${global_vars[$varname]}"
elif [ x${vars[$varname]+set} = xset ]
then
result+="${vars[$varname]}"
else
result+="\${$varname}"
fi
string="${string#"\${$varname}"}"
;;
*)
result+="${string:0:1}"
string="${string:1}"
;;
esac
done
echo "$result"
}
# Usage: pkg_get_keyword PKG KEYWORD
#
# Outputs the value of KEYWORD defined for PKG. The .pc file for PKG must
# exist.
function pkg_get_keyword () {
local pkg="$1"
local keyword="$2"
local -A vars
local line
local varlines
local varname varvalue
local raw_value
local result
while read line
do
case "$line" in
*=*)
varname="${line%%=*}"
raw_value="${line#*=}"
varvalue="$(echo "$varlines" | substitute_vars
"$raw_value")"
vars[$varname]="$varvalue"
if [ -n "$varlines" ]
then
varlines+=$'\n'"$varname=$varvalue"
else
varlines="$varname=$varvalue"
fi
;;
"$keyword:"*)
raw_value="${line#"$keyword:"}"
result="$(echo "$varlines" | substitute_vars
"$raw_value")"
;;
esac
done < "$(pkg_pcfile "$pkg")"
echo $result
}
# Usage: pkg_get_var PKG VAR
#
# Outputs the value of the variable named VAR defined for PKG. If there is a
# global variable named VAR, it takes precedence over any variables defined in
# the .pc file of the package. The .pc file for PKG must exist.
function pkg_get_var () {
local pkg="$1"
local var="$2"
local -A vars
local line
local varlines
local varname varvalue
local raw_value
if [ x${global_vars[$var]+set} = xset ]
then
echo "${global_vars[$var]}"
return 0
fi
while read line
do
case "$line" in
*=*)
varname="${line%%=*}"
raw_value="${line#*=}"
varvalue="$(echo "$varlines" | substitute_vars
"$raw_value")"
vars[$varname]="$varvalue"
if [ -n "$varlines" ]
then
varlines+=$'\n'"$varname=$varvalue"
else
varlines="$varname=$varvalue"
fi
;;
esac
done < "$(pkg_pcfile "$pkg")"
echo "${vars[$var]}"
}
# Usage: pkg_list_vars PKG
#
# Outputs the names of all variables defined for PKG. The .pc file for PKG must
# exist.
function pkg_list_vars () {
local pkg="$1"
local line
while read line
do
case "$line" in
*=*) echo "${line%%=*}" ;;
esac
done < "$(pkg_pcfile "$pkg")"
}
function pkg_name () {
pkg_get_keyword "$1" "Name"
}
function pkg_description () {
pkg_get_keyword "$1" "Description"
}
function pkg_url () {
pkg_get_keyword "$1" "URL"
}
function pkg_version () {
pkg_get_keyword "$1" "Version"
}
function pkg_requires () {
pkg_get_keyword "$1" "Requires"
}
function pkg_requires_private () {
pkg_get_keyword "$1" "Requires.private"
}
function pkg_conflicts () {
pkg_get_keyword "$1" "Conflicts"
}
function pkg_libs () {
pkg_get_keyword "$1" "Libs"
}
function pkg_libs_private () {
pkg_get_keyword "$1" "Libs.private"
}
function pkg_cflags () {
pkg_get_keyword "$1" "Cflags"
}
function print_pkg_not_found () {
local pkg="$1"
echo "Package $pkg was not found in the pkg-config search path.
Perhaps you should add the directory containing \`$pkg.pc'
to the PKG_CONFIG_PATH environment variable
No package '$pkg' found"
}
function print_version_mismatch () {
local pkg="$1"
local cmp="$2"
local version="$3"
echo "Requested '$pkg $cmp $version' but version of $(pkg_name $pkg) is
$(pkg_version $pkg)"
}
function print_cmp_without_version () {
local pkg="$1"
echo "Comparison operator but no version after package name '$pkg'"
}
function print_var_without_value () {
echo "--define-variable argument does not have a value for the variable"
}
function print_var_already_defined () {
local var="$1"
echo "Variable '$var' defined twice globally"
}
# Usage: check_pkg_list PKG...
#
# Checks that all packages in the given list exist. Each package may be
# followed by a comparison sign and a version specification, in which case the
# function also checks that the package version matches this requirement.
# Packages are separated by commas and/or whitespace.
#
# If all packages are found and their versions match the given requirements, a
# list of packages (without the version requirements) is printed to stdout and
# the function returns 0. Otherwise, an error message is printed to stderr and
# the function returns 1.
function check_pkg_list () {
local pkg cmp arg
local list
local oldifs=$IFS
IFS=$IFS,
for arg in $*
do
IFS=$oldifs
if [ -z "$pkg" ]
then
pkg=$arg
elif [ -z "$cmp" ]
then
case $arg in
"="|"!="|"<"|">"|"<="|">=") cmp=$arg ;;
*)
# $pkg contains the name of the
# package, and it has no version
# requirements. Just check that the
# package exists. $arg is the name of
# the next package in the list.
if ! pkg_pcfile $pkg >/dev/null
then
print_pkg_not_found "$pkg" >&2
return 1
fi
list="$pkg $list"
pkg=$arg
;;
esac
else
# $pkg and $cmp are set, which means we had encountered
# a package name followed by a comparison operator.
# $arg is now the version string. Check that the
# package exists and compare its version to $arg.
if ! pkg_pcfile $pkg >/dev/null
then
print_pkg_not_found "$pkg" >&2
return 1
fi
if ! cmp_versions "$(pkg_version $pkg)" $cmp $arg
then
print_version_mismatch $pkg $cmp $arg >&2
return 1
fi
list="$pkg $list"
pkg=
cmp=
fi
done
if [ -n "$cmp" ]
then
print_cmp_without_version $pkg >&2
return 1
fi
# check the last package if any
if [ -n "$pkg" ]
then
if ! pkg_pcfile $pkg >/dev/null
then
print_pkg_not_found "$pkg" >&2
return 1
fi
list="$pkg $list"
fi
echo $list
}
# Usage: remove_dups_keep_first ARG...
#
# Prints all given arguments to stdout with any duplicates removed. Only the
# first occurence of each duplicate is printed.
function remove_dups_keep_first () {
local arg
local -A seen
local result
for arg
do
if [ x${seen[$arg]+set} != xset ]
then
result="$result $arg"
seen[$arg]=1
fi
done
echo $result
}
# Usage: remove_dups_keep_last ARG...
#
# Prints all given arguments to stdout with any duplicates removed. Only the
# last occurence of each duplicate is printed.
function remove_dups_keep_last () {
local arg
local reversed
local -A seen
local result
# reverse the order of the arguments
for arg
do
reversed="$arg $reversed"
done
# prepend each newly-seen argument to $result, thereby restoring the
# original order of the arguments
for arg in $reversed
do
if [ x${seen[$arg]+set} != xset ]
then
result="$arg $result"
seen[$arg]=1
fi
done
echo $result
}
# five different types of flags:
# I - cflags that start with -I
# o - cflags that do not start with -I
# l - libs that start with -l
# L - libs that start with -L
# O - libs that do not start with -[lL]
optflags_cflags_I=I
optflags_cflags_other=o
optflags_libs_l=l
optflags_libs_L=L
optflags_libs_other=O
nonoptions=
for arg
do
# handle options that take an argument
if [ x${optname+set} = xset ]
then
arg="$optname=$arg"
unset optname
fi
case "$arg" in
--variable | --define-variable | --atleast-pkgconfig-version)
optname=$arg
continue
;;
esac
case "$arg" in
--version)
echo "$this_version"
exit 0
;;
--atleast-pkgconfig-version=*)
cmp_versions $this_version ">=" "${arg#*=}"
exit
;;
--modversion) opt_modversion=1 ;;
# don't care, but configure uses them
--print-errors) ;;
--silence-errors) ;;
--short-errors) ;;
--errors-to-stdout) ;;
--cflags) opt_flags+=$optflags_cflags_I$optflags_cflags_other ;;
--cflags-only-I) opt_flags+=$optflags_cflags_I ;;
--libs)
opt_flags+=$optflags_libs_l$optflags_libs_L$optflags_libs_other ;;
--libs-only-L) opt_flags+=$optflags_libs_L ;;
--libs-only-l) opt_flags+=$optflags_libs_l ;;
--variable=*) opt_variable="${arg#*=}" ;;
--define-variable=*)
optarg="${arg#*=}"
case "$optarg" in
*=*)
varname="${optarg%%=*}"
varvalue="${optarg#*=}"
;;
*)
print_var_without_value >&2
exit 1
esac
if [ x${global_vars[$varname]+set} = xset ]
then
print_var_already_defined "$varname" >&2
exit 1
fi
global_vars[$varname]="$varvalue"
;;
--print-variables) opt_print_variables=1 ;;
# I don't quite understand what this option is for, pkg-config
# seems to check for the existence of the given packages with
# or without it. But configure scripts use it anyway, so it
# must be recognized here.
--exists) ;;
--static) opt_static=1 ;;
-*)
echo "$arg: unknown option" >&2
exit 1
;;
*) nonoptions="$nonoptions $arg" ;;
esac
done
if [ x${optname+set} = xset ]
then
echo "$optname: missing argument" >&2
exit 1
fi
# check that all given packages exist and have the required versions
cmd_pkgs=$(check_pkg_list $nonoptions)
[ $? -eq 0 ] || exit 1
if [ x${opt_modversion+set} = xset ]
then
for pkg in $cmd_pkgs
do
pkg_version $pkg
done
fi
if [ x${opt_flags+set} = xset ]
then
# Create a list of all packages required by $cmd_pkgs, and all their
# required packages, recursively.
# To implement recursion, use two lists. $pkg_list is the final result,
# which contains all required packages. $add_list is a list of packages
# which will be added to $pkg_list after they will be checked for their
# own required packages.
#
# Newly seen packages are first added to $add_list. Then, the required
# packages of all packages in $add_list are put in a temporary list.
# Then, all packages in $add_list are added to $pkg_list and $add_list
# is set to the temporary list. Thus, the required packages of all
# packages which are listed in $pkg_list are guaranteed to be either in
# $pkg_list (if they were already checked for their own dependencies)
# or in $add_list (if they are about to be checked in the next
# iteration). The process continues until there are no packages in
# $add_list, which means that there are no more packages to check for
# dependencies.
pkg_list=
add_list=$cmd_pkgs
while [ -n "$add_list" ]
do
requires=
for pkg in $add_list
do
requires="$requires $(pkg_requires $pkg)"
done
tmp=$(check_pkg_list $requires)
[ $? -eq 0 ] || exit 1
pkg_list="$pkg_list $add_list"
add_list=$tmp
done
# do the same, but this time add also packages listed in
# "Requires.private" for each package
pkg_list_with_private=
add_list=$cmd_pkgs
while [ -n "$add_list" ]
do
requires=
for pkg in $add_list
do
requires="$requires $(pkg_requires_private $pkg)"
requires="$requires $(pkg_requires $pkg)"
done
tmp=$(check_pkg_list $requires)
[ $? -eq 0 ] || exit 1
pkg_list_with_private="$pkg_list_with_private $add_list"
add_list=$tmp
done
# As an optimization, remove duplicates from the package lists so that
# each package will be checked only once. We must keep the last
# occurence of each package in order to preserve dependency order.
pkg_list=$(remove_dups_keep_last $pkg_list)
pkg_list_with_private=$(remove_dups_keep_last $pkg_list_with_private)
# get a list of flags for all required packages
pkg_cflags=
for pkg in $pkg_list_with_private
do
pkg_cflags="$pkg_cflags $(pkg_cflags $pkg)"
done
pkg_libs=
if [ x${opt_static+set} = xset ]
then
for pkg in $pkg_list_with_private
do
pkg_libs="$pkg_libs $(pkg_libs $pkg)"
pkg_libs="$pkg_libs $(pkg_libs_private $pkg)"
done
else
for pkg in $pkg_list
do
pkg_libs="$pkg_libs $(pkg_libs $pkg)"
done
fi
# classify all flags and remove ignored flags
pkg_cflags_I=
pkg_cflags_other=
pkg_libs_l=
pkg_libs_L=
pkg_libs_other=
for flag in $pkg_cflags
do
case $flag in
-I/usr/include) ;;
-I*) cflags_I="$cflags_I $flag" ;;
*) cflags_other="$cflags_other $flag" ;;
esac
done
for flag in $pkg_libs
do
case $flag in
-L/usr/lib) ;;
-L*) libs_L="$libs_L $flag" ;;
-l*) libs_l="$libs_l $flag" ;;
*) libs_other="$libs_other $flag" ;;
esac
done
# If libx depends on liby, then -lx should appear before -ly on the
# command-line. Therefore, it is important that the last occurence of
# each duplicate, rather than the first, will be used when removing
# duplicate flags in $libs_l.
cflags_I=$(remove_dups_keep_first $cflags_I)
cflags_other=$(remove_dups_keep_first $cflags_other)
libs_L=$(remove_dups_keep_first $libs_L)
libs_l=$(remove_dups_keep_last $libs_l)
libs_other=$(remove_dups_keep_first $libs_other)
# now print all flags requested (in the same order that the standard
# pkg-config prints them)
out=
[[ $opt_flags == *${optflags_cflags_other}* ]] && out="$out
$cflags_other"
[[ $opt_flags == *${optflags_cflags_I}* ]] && out="$out $cflags_I"
[[ $opt_flags == *${optflags_libs_other}* ]] && out="$out $libs_other"
[[ $opt_flags == *${optflags_libs_L}* ]] && out="$out $libs_L"
[[ $opt_flags == *${optflags_libs_l}* ]] && out="$out $libs_l"
echo $out
fi
if [ x${opt_variable+set} = xset ]
then
value=
for pkg in $cmd_pkgs
do
value="$value $(pkg_get_var $pkg "$opt_variable")"
done
echo $value
fi
if [ x${opt_print_variables+set} = xset ]
then
for pkg in $cmd_pkgs
do
pkg_list_vars $pkg
done
fi
exit 0
--
http://linuxfromscratch.org/mailman/listinfo/lfs-dev
FAQ: http://www.linuxfromscratch.org/faq/
Unsubscribe: See the above information page