What, exactly, does 'pkg-config --exists' do? - c

I recently tried installing an app from source (dwb on Debian Wheezy if it makes a difference). While installing, it would choke on some dependency problem, but to me it seemed I was meeting that dependency. I wasn't the only person with this problem. Which got me poking around in the project's config.mk.
In config.mk I found several blocks that looked like this:
REQPKG=foo
ifeq ($(shell pkg-config --exists $(REQPKG) && echo 1),1)
LIBS=$(REQPKG)
else
$(error Cannot find $(REQPKG))
endif
Which seems very counter intuitive to me. I searched and searched. Read the man page, consulted google. Read everything I could about pkg-config and found nothing. The man page is particularly frustrating as it simply lists '--exists' as a flag and gives no description (I guess it's as self explanatory as --help).
I began thinking that Debian's pkg-config had a bug where it returned inverse results. But, as it turns out 'pkg-config --exists' is supposed to return 0 (i.e. False) when the requested package is present.
Why is this? Wouldn't it produce cleaner code to return True/False or 1/0 indicating the presence of the required package? You could do this:
if (shell pkg-config --exists foo)
/*do things*/
else
/*throw error*/
endif
Instead of what looks to me like:
ifeq (($(not(True) && True)),True)
/*do things*/
else [...]
What am I missing about pkg-config? Does it act this way because it's a shell utility and some strange convention regarding them?
Also, why is the documentation so sparse? Am I missing a resource that would give the info I was looking for?
PS I don't have any real experience with C, so if my example code looks weird...

pkg-config --exists doesn't create any output it simply returns with an exit code of 0 for success and 1 for failure.
&& in the shell runs the right-hand side when the left-hand side returns success.
So that line is running pkg-config --exists and iff it returns true (return code of 0) it then echos a 1 as output. That 1 is then compared against the 1 in the ifeq.
And as to why you can't do something simpler here the answer is because make (when not in target rules) doesn't deal with program return codes. Specifically $(shell) pays the return code of the shell command being run no attention at all.
Personally, I'd probably have written that test as either:
ifeq ($(shell pkg-config --exists $(REQPKG); echo $?),0)
or something like
REQPKG=foo
REQPKG:=$(shell pkg-config --exists $(REQPKG) && echo '$(REQPKG)')
ifneq ($(REQPKG),)
LIBS=$(REQPKG)
else
$(error ...)
fi

Lots of UNIX commands and library functions return 0 on success. It's the "sensible default" in that world.
That's why, for example,. the prototypical "Hello, world!" in C ends with "return 0;" and not "return 1" - nonzero codes are considered erroneous. (Not that anyone is probably going to look at the return value of your HW, but still...)

Etan provides very good insight. But, there is one clarification regarding his recommendation:
ifeq ($(shell pkg-config --exists $(REQPKG); echo $?),0)
In my case, the return code is not retrievable with echo $?, so the comparison always fails. The line works directly in a terminal, but not within the shell function. I had to resort to the confusing && echo 0
Anyway, I learned a lot from his description, and thank him for that.

This seems cleaner to me:
pkg-config --exists gtk+-3.0 && echo "Installed" || echo "Not installed"

Related

How can I generate a Syntastic config file using premake?

Syntastic is a source code linter plugin for the Vim editor.
It does various syntax and heuristic checks, using external tools. In the case of C and C++ code, this frequently involves running a compiler on the code.
In order to invoke the compiler, Syntastic reads a config file that contains command-line arguments that should be used to invoke the compiler.
Obviously, the "real" compilation in the project is handled by premake but this means there are potentially two sources of truth -- the compiler flags written into the Syntastic config file, and the compiler flags written by premake into the build scripts.
I'd like to resolve this by having premake generate the compiler flags in the Syntastic config file, also.
This seems like a fairly straightforward task with various possible approaches -- generate a fake compiler, invent a pre-build task, etc. But I don't know enough about the innards of premake to know which of these approaches is the right one.
How can I get premake to generate my syntastic.config file?
I looked into a few different kinds of projects under premake5 -- there have been several added to the documentation recently. None of them quite got me there.
The problem is that what I want to do is either "always create the file" or "create the file when the premake5.lua file changes." That makes things difficult, but not totally impossible.
However, the generated file is not an input file for any of the projects, so that apparently pushes it over the edge from difficult to "not possible ATM".
I tried doing what premake calls Custom Build Commands, but this needs the generated file to be a piece of source code it can use to build with. If this isn't the case, apparently the dependency graph breaks and the makefile is generated without a dependency on the file. :(
In order to just get this working, I opted to just regenerate the file all the time using a prebuild command:
prebuildcommands {
"$(SILENT) "
..repo_dir.."etc/write-syntastic-config.sh $(ALL_CFLAGS)"
.." > "
..repo_dir.."etc/syntastic.config"
}
The key value in this is $(ALL_CFLAGS), the variable used by the Makefile generator to hold the stuff I want.
Syntastic's config file and its use of the config file is a bit touchy. It doesn't accept -L and -l options, or perhaps gcc doesn't accept them in whatever mode it is invoked with. Regardless, some filtering is required the args can be used.
Also, syntastic processes the lines looking specifically for -I, which it then handles in a special way: the include paths are treated as either absolute, or relative to the config file.
Finally, syntastic doesn't appear to know about -isystem, another include-directory option for gcc. So that has to be handles differently.
If anyone cares, here's the script I'm using:
etc/write-syntastic-config.sh
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
PENDING=""
PENDING_DIR=false
printf '# Generated by %s\n' "$(basename "$0")"
CFG_FILE_DIR="$(dirname "$0")"
for ARG in "$#"
do
#printf >&2 "arg=>%s<\\n" "$ARG"
case "$ARG" in
-I| \
-L)
PENDING="$ARG"
PENDING_DIR=true
;;
-isystem)
# -isystem is like -I, except syntastic doesn't know it
printf '%s\n' "$ARG"
PENDING_DIR=true
;;
-I*| \
-L*) PENDING_DIR=true
PENDING="${ARG:0:2}"
ARG="${ARG/#-?/}"
;;& #<-- Resume matching cases (i.e, "goto next case")
*) if $PENDING_DIR
then
ARG="$(realpath -m \
--relative-to "$CFG_FILE_DIR" \
"$ARG")"
fi
case "$PENDING" in
-L) : ignore it ;;
"") printf '%s\n' "$ARG" ;;
*) printf '%s %s\n' "$PENDING" "$ARG" ;;
esac
PENDING=""
PENDING_DIR=false
;;
esac
done
exit 0

PKG_CHECK_MODULES macro not properly expanding, while i do have pkg-config installed and working

BEFORE YOU LINK TO: Using the pkg-config macro PKG_CHECK_MODULES failing
this does not solve my solution. i do have pkg-config installed. when i type aclocal --print, the first thing it gives is main::scan_file() called too early to check prototype at /usr/local/bin/aclocal line 618. it does that too in ./bootstrap.
then it gives the directory /usr/local/share/aclocal
for one or another reason, the macro still doesnt work. i am on Artix linux (an Arch like distro), with automake 1.16.2-3 and pkg-conf 1.7.3-1. the output of ./configure, config.log and ./bootstrap are: https://pastebin.com/NY1GgtFF (configure), https://pastebin.com/iDAUXRv3 (config.log) and https://pastebin.com/aRVw00Ex (bootstrap)
the macro expansion fails no matter what. do you have any ideas? how would i set the m4_pattern_allow flag? this error occurs both at openbox, and lxterminal. i havent tried any other programs yet where i encounter this error, but i dont think it matters at which program it happens
i really hope i provided enough information, this is my first post, so i hope i did everything right
pkg-conf is not the same as pkg-config — the former is a low-deps reimplementation and does not come with the pkg.m4 file you need to have the macro installed.
If the software you're trying to build comes with a copy of pkg.m4 bundled in the repository and/or tarball, you may just need to add -I m4 to the invocation of aclocal, otherwise you need to fetch the pkg.m4 file from a pkg-config distribution.

Unexpected EOF while looking for matching '`', but only when run from makefile?

My shell script is having an issue, however, it's a bit too long to post here. Here is a link to it: http://pastebin.com/jh9fHJ2e
Does anyone have any idea why I would be getting these two errors? I'm calling it from a makefile like so:
.PHONY: compile clean
compile:
$(shell ./compile.sh)
clean:
$(shell ./clean.sh)
However, if I run it manually (`./compile.sh`), I do not get the error. What am I doing wrong? I worry that the quotes are coming from the output lines: make: Nothing to be done for 'default. due to the weird quote lapse in there, but how do I prevent that if Make is what's causing it?
Hopefully this is a reasonable, non-duplicate question. It's not the same as other similar ones as I've checked. I'm happy to remove if it's a bad question, please simply let me know.
You should not use $(shell ...) here. The shell function is like backticks in a shell script: the result of the execution is the standard output of the command invoked. If your command prints some text, that will try to be executed as a shell script.
A recipe is already run in a shell, so you don't need the shell function:
.PHONY: compile clean
compile:
./compile.sh
clean:
./clean.sh
I wish I could understand why so many people try to use the shell function in recipes like this... I feel like there must be some "intro to make" out there using very bad examples.
Generally you wouldn't create separate shell scripts, anyway: you'd put the compile and clean commands right into the recipe. What's the point of having a makefile where you type "make clean" when all it does is run a shell script, so you could just type "./clean.sh" instead and get the same behavior?
The make syntax $(shell ... ) runs a shell command and substitutes the output of the shell command into the make script at that point. So when you have a rule like
compile:
$(shell ./compile.sh)
it will run the script compile.sh and take the output of that shell script as the actions (shell commands) to run to complete the compile target. Since it looks like your compile.sh script does the actual compile, outputting messages about what it is doing, when make then tries to run those messages as shell commands, the shell barfs (giving you error messages).

C - program compiling, but unable to provide arguments

I'm on a Mac and in terminal I'm compiling my program
gcc -Wall -g -o example example.c
it compiles (there are no errors), but when I try to provide command line arguments
example 5 hello how are you
terminal responds with "-bash: example: command not found"
how am supposed to provide the arguments I want to provide after compiling?
Run it like this with path:
./example 5 hello how are you
Unless the directory where the example binary is part of the PATH variable, what you have won't work even if the binary you are running is in the current directory.
It is not a compilation issue, but an issue with your shell. The current directory is not in your PATH (look with echo $PATH and use which to find out how the shell uses it for some particular program, e.g. which gcc).
I suggest testing your program with an explicit file path for the program like
./example 5 hello how are you
You could perhaps edit your ~/.bashrc to add . at the end of your PATH. There are pro and conses (in particular some possible security issues if your current directory happens to be sometimes a "malicious" one like perhaps /tmp might be : bad guys might put there a gcc which is a symlink to /bin/rm so you need to add . at the end of your PATH if you do).
Don't forget to learn how to use a debugger (like gdb). This skill is essential when coding in C (or in C++). Perhaps consider also upgrading your gcc (Apple don"t like much its current GPLv3 license so don't distribute the recent one; try just gcc -v and notice that the latest released GCC is today 4.8.1).
./example 5 Hello how are you is the syntax you're looking for.
This article lends a good explanation as to why this is important.
Basically, when you hit Enter, the shell checks to see if the first set of characters is an absolute path. If it's not, it checks the PATH variable to find executables with the name of the command you are trying to run. If it's found, it will be run, but otherwise it will crash and burn and you will become very sad.

Determine if C library is installed on Unix

As a follow up question to my last one, is there any simple way to tell if a given C library is installed on a given machine (not programattically, just as a once off sort of thing)? I need to use libuuid, but I'm not sure if it's installed on the machines in question. The only two ways I can think of are:
1) Try to compile the code there (more work than I'd like to do)
2) Try something like "man libuuid" although seems like this wouldn't always be reliable if for some reason the manpages didn't get installed.
Is there some better other way?
Have you considered using autoconf? It's designed to check and see whether the build environment is set up correctly.
The simplest way is to invoke ld with the -l option. This will effectively test the existence of the library, searching the standard library locations automatically:
$ ld -luuid
ld: warning: cannot find entry symbol _start; not setting start address
$ echo $?
0
$ ld -luuidblah
ld: cannot find -luuidblah
$ echo $?
1
# so...
$ ld -luuid 2>/dev/null && echo "libuuid exists" || echo "libuuid not found"
EDIT
As dreamlax pointed out, this does not work for all unix variants. I don't know if it will work on all unixes (I've tested linux and OpenBSD) but you can try this instead:
$ echo "int main(){}" | gcc -o /dev/null -x c - -luuid 2>/dev/null
$ echo $?
0
$ echo "int main(){}" | gcc -o /dev/null -x c - -luuidblah 2>/dev/null
$ echo $?
1
Here's what I did using autoconf, which I'm showing here as a solid example for whoever else might come by next:
I created the file configure.ac which contained the following:
AC_INIT(package, 1.1, email)
AC_CHECK_LIB(uuid, uuid_generate_random, [echo "libuuid exists"], [echo "libuuid missing"])
I then ran the following commands in order (same folder I made configure.ac):
autoconf
./configure
At the end of the configure, it spat back whether or not it had found uuid_generate_random in the uuid library. Seemed to work perfectly (although unfortunately, two of the OSes were missing the library, but that's a whole other problem).
For anybody who may find this after the fact, the AC_INIT arguments here are throwaways and you can copy them wholesale. The arguments to AC_CHECK_LIB are: library name, the name of a function in that library, what to do on success, what to do on failure.
Even though Mehrdad's answer wasn't quite as helpful as I would have liked (i.e. to not have spent time trolling the docs) it seems to be the correct one and I'll be accepting it. mhawke: I really liked your answer, but I wasn't quite sure how to test to make sure it worked. It seemed to on SunOS, but always said no on the other two (AIX, HPUX) and I couldn't seem to come up with a library off the top of my head I could guarantee it would find.
Thanks for the help guys.
The autotools, as mentioned, checks for symbols within libraries. The way it does it is somewhat simple. As you mention in 1), autoconf, and its result the configure script, basically creates a dummy c program and attempts to link with the library in question. If it works, the library works, if it fails, obviously it wont work. Autoconf looks for specific symbols/function names in the library though.

Resources