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