Makefile ifeq fails - makefile

I'm new to makefile and find using findstring rather hard.
Here's my code:
ZSHRC="$HOME/.zshrc"
install:
ifneq($(findstring "CADANSE",$(ZSHRC) a),)
#echo "done $(a)"
endif
Wherever I move the comma I get errors, most frequently:
syntax error near unexpected token `,'
I made sure my tabs are 4 proper spaces and tried a couple of tutorials to get it working but to no avail.
I need to check if the string BEGIN.CADANSE is in .zshrc because it loads extra shell methods. I'm trying to make an installer for it, makefile is not mandatory but I've been required to investigate it.
Thank you for any help.
OS is latest MacOSX and shell is oh-my-zsh.
UPDATE - Solution
To address my issue I'm going through a patch now, instead of a grep:
installcadanse: docs
#cat ~/.zshrc lib/Cadanse/template/dot-rc | diff -u ~/.zshrc - > PATCH_CADANSE ; patch ~/.zshrc PATCH_CADANSE
#source ~/.zshrc ; echo "Cadanse should be installed in your shell. Please check ~/.zshrc for mentions of Cadanse."

Zeroth of all, what do you mean by "my tabs are 4 proper spaces"? The action part of a rule must be indented by a tab. Not 4 spaces, not 8 spaces, but a literal tab character.
First, the commands associated with a rule are passed to the shell (after variable expansion). If you want to test a condition in an action, you have to write a shell command for it, not a make command.
Second, findstring performs a simple substring search. It does not open any files or read their contents.
Third, I'm pretty sure "$HOME/.zshrc" in make means "${H}OME/.zshrc" (i.e. it'll look for a variable called H). Also, those quotes are taken literally.
To search a file for a given string in a shell script, you can use grep:
.PHONY: install
install:
#if grep -q CADANSE ~/.zshrc; then \
echo done; \
fi

Related

Process an external script using Makefile variables

I'm trying to include an external preprocess script to my current Makefile flow.
The external script location is /home/preprocessscript.sh, and content is:
cd ${path}; rm -rf *
Makefile content is:
path=/home/rubbish
clean:
cat ${preprocessscript} >> clean.sh
I execute the command by:
make clean preprocessscript=/home/preprocessscript.sh
./clean.sh
The problem is clean.sh doesn't get path. Its content will be:
cd ; rm -rf *
How do I pass the make variable to preprocessscript?
The problem is clean.sh doesn't get path. Its content will be:
cd ; rm -rf *
I'm sorry about the contents of your home directory.
Anyway, no. The cat command will output the contents of the original file verbatim. It will not interpret them in any way, and in particular, it will not attempt to expand shell-style variable references within.
Supposing that you have faithfully represented your situation, what you actually see is that when you run the resulting clean.sh as a script, the ${path} within expands to nothing. This is not particularly surprising, because nowhere in what you've presented is an environment variable named path defined. You have defined a make variable of that name, but that's not the same thing, and it anyway would not have an effect persisting past the end of the make run to when the generated script is executed.
I guess the idea is that you want to use the original file as a template to generate content for clean.sh. There are lots of ways to do that sort of thing, but the one closest to your current attempt would probably be to output (shell) variable assignments into the generated script, like so:
path = /home/rubbish
clean:
echo "path='$(path)'" >> clean.sh
cat ${preprocessscript} >> clean.sh
The generated script lines would then be
path='/home/rubbish'
cd ${path}; rm -rf *
Note also that there appear to be several other issues with the general approach presented in the question, but I am focusing my comments on the specific question asked.
You can export a make variable to the shell before running your command and use envsubst to expand the variables in the file as so:
path=/home/rubbish
clean:
export path=${path}; envsubst < ${preprocessscript} >> clean.sh
notice that the export has to be on the same recipe line as the command. You can also pipe the output of a command to envsubst: cat ${preprocessscript} | envsubst >> clean.sh. This might be useful if you're trying to do something more complicated like only print a few lines, etc.

Overriding make variables containing spaces with MAKEFLAGS

I have a bash script to run my Makefile-based project through Include-What-You-Use (IWYU) which looks as follows:
#!/bin/bash
export MAKEFLAGS="-k CXX=iwyu -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=qt5_4.imp"
build.sh
The build.sh script sets a couple of environment variables, prepares other parts of the build environment and will then eventually run make.
At first sight this seems to work and do its job. However closer inspection showed, that only CXX=iwyu is actually used in the build. The command line options for IWYU get dropped.
I tried various modifications of my call to fix this, however none seemed to solve the problem.
With
export MAKEFLAGS="-k CXX=iwyu\ -Xiwyu\ --transitive_includes_only\ -Xiwyu\ --mapping_file=qt5_4.imp"
the command line options are no longer dropped, but now suddenly -k seems to get dropped somewhere and my build (because of the missing -k) is terminated early with failure.
With
export MAKEFLAGS="-k CXX='iwyu -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=qt5_4.imp'"
I'm flooded with /bin/sh: 1: Syntax error: Unterminated quoted string which looks like the ending ' is somehow dropped.
Are there any other ways I have to escape and/or quote the spaces in my export to fix this?
You can create a script (once?) to run iwyu — be careful, single quotes and double quotes are not interchangeable:
echo 'iwyu -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=qt5_4.imp "$#"' > ./run.iwyu
chmod +x ./run.iwyu
and then run:
make -k CXX="$PWD/run.iwyu"
or:
export MAKEFLAGS="-k CXX=$PWD/run.iwyu"
This sidesteps the whole problem of spaces in the arguments. As shown, you specify the full path for the run.iwyu script just in case your make process changes directories. If you put the run.iwyu script in a directory on your PATH, you don't need to specify the full path to the script. You could prefix the command line in the script with exec if you like.

Can I make tab-completion filter files by extension?

Assume myprogram only consumes *.data files as command line arguments. In terminal, when we do
$ myprogram <tab>
we want only the *.data files to be listed for tab auto-complete. How is this behavior achieved? The shell being used is Bash.
Option 1
Type the following into your bash shell
complete -f -X '!*.data' myprogram
the -f option tells complete to only complete based on file names, not directories. the -X option allows you to specify the filter pattern.
Option 2
Note: This solution is global. It will affect tab-completion in every directory and on every command (meaning things like cd or rm, as well as myprogram). It works by allowing you to specify file extensions that will not appear in tab-complete. This is not exactly what you asked for, but if there aren't many files other than *.data in your working directory, excluding all the options won't be too much of a pain. For both these reasons this option is probably not what you want but it is still worth noting.
In the file ~/.bash_profile add the line
FIGNORE=".py:.txt:.out:.exe:.c:<etc>"
The syntax there is to create a colon-separated list of the file extensions you want to ignore. After saving the new .bash_profile you must type . ~/.bash_profile for the changes you made to take effect.
Further info
For more info about the complete command check out Programmable Completion Builtins in the Bash manual.
Better use _filedir instead of "raw" complete.
Justification
grep _filedir -r $(pkg-config --variable=completionsdir bash-completion) | wc -l
tilde (~) paths are being expanded
Prefixes are removed for directories, i.e. for /home/tux/Do<TAB><TAB>, the list you get as a reply removes '/home/tux' and is thus much more compact
Easier to implement and more failsafe
MWE
Write a file "completions/myprogram", which you source by . completions/myprogram. Content:
_myprogram()
{
# init bash-completion's stuff
_init_completion || return
# fill COMPREPLY using bash-completion's routine
_filedir '#(data)'
}
complete -F _myprogram myprogram

Is it feasible to store a string which includes a space to the variable in bash?

I want to put my ~/Library/Application Support/ directory to a variable in my ~/.bash_profile` to make it easier to reference from within Terminal. I first attempted to define it as follows:
export L=~/Library/Application\ Support
However, when I tried to source ~/.bash_profile and then called ls $L, I got an error: /Users/username/Library/Application: Not a directory.
However, no matter how I define it I cannot define it properly, as far as I came up with the way to define it. Here's the list that I tried, but none of them worked properly.
~/Library/Application Support
"~/Library/Application Support"
"~/Library/Application\ Support"
So is it feasible to store a string which includes a whitespace to a variable in bash to begin with?
Your export statement is fine; the space is properly escaped. You just need to quote the expansion of the parameter, so that bash gives a single argument to the ls command:
ls "$L"

Shell variable with spaces , quoting for single command line option

Autoconf scripts have trouble with a filename or pathname with spaces. For example,
./configure CPPFLAGS="-I\"/path with space\""
results in (config.log):
configure:3012: gcc -I"/path with space" conftest.c >&5
gcc: with: No such file or directory
gcc: space": No such file or directory
The compile command from ./configure is ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' and I am not able to modify this (I could perhaps, but working around autoconf in this way is not a general solution).
I think it comes down to getting a shell variable that contains spaces to be parsed as a single command line variable rather than split at spaces. The simplest shell example I can come up with is to create a file with spaces and attempt to list is with ls with a shell variable as the argument to ls:
$ touch "a b"
$ file="a b"
$ ls $file
ls: a: No such file or directory
ls: b: No such file or directory
This works, but is illegal since in autoconf I can't modify the shell code:
$ ls "$file"
a b
None of the following attempts at quoting things work:
$ file="\"a \"b"; ls $file
ls: "a: No such file or directory
ls: b": No such file or directory
$ file="a\ b"
$ file="a\\ b"
$ file="`echo \\"a b\\"`"
and so on.
Is this impossible to accomplish in shell scripts? Is there a magical quoting that will expand a shell variable with spaces into a single command line argument?
You should try to set the $IFS environment variable.
from man bash(1):
IFS - The Internal Field Separator that is used for word splitting
after expansion and to split lines into words with the read builtin
command. The default value is ''space tab newline''.
For example
IFS=<C-v C-m> # newline
file="a b"
touch $file
ls $file
Don't forget to set $IFS back or strange things will happen.
if you give command
gcc -I"x y z"
in a shell then certainly the single command line parameter "-Ix y z" will be passed to gcc. There is no question to that. That's the whole meaning of double quotes: things inside double quotes are NOT subject to field splitting, and so not subject to $IFS either, for instance.
But you need to be careful about the number of quotes you need. For instance, if you say
file="a b" # 1
and then you say
ls $file # 2
what happens is that the file variable's contents are 'a b', not '"a b"', because the double quotes were "eaten" when line 1 was parsed. The replaced value is then field-separated and you get ls on two files 'a' and 'b'. The correct way to get what you want is
file="a b"; ls "$file"
Now the problem in your original case is that when you set a variable to a string that CONTAINS double quotes, the double quotes are later not interpreted as shell quote symbols but just as normal letters. Which is why when you do something like
file="\"a b\""; ls $file
actually the shell tokenizes the contents of the file variable into '"a' and 'b"' when the ls command is analyzed; the double quote is no longer a shell quote character but just part of the variable's contents. It's analogous to that if you set
file="\$HOME"; ls $file
you get an error that '$HOME' directory does not exist---no environment variable lookup takes place.
So your best options are
Hack autoconf
Do not use path names with spaces (best solution)
Using space in directory names in the Unix world is simply asking for trouble. It's not just the problem of quoting in shell scripts (which needs to be done right anyway): some tools simply cannot cope with spaces in filenames. For instance, you can't (portably) write a Makefile rule that says build baz.o from foo bar/baz.c.
In the case of CPPFLAGS above, I would try one of the following (in order of preference):
Fix the system not use use any space in directory names.
Write a small wrapper around the compiler and call ./configure CC=mygcc. In that case mygcc might be:
#!/bin/sh
gcc "-I/foo bar/include" "$#"
Create a symbolic link (e.g., /tmp/mypath) to the dreaded path and use CPPFLAGS=-I/tmp/mypath.
You want to quote the entire argument, in either of these ways:
./configure "CPPFLAGS=-I/path with space"
./configure CPPFLAGS="-I/path with space"
The ./configure command then sees a single argument
"CPPFLAGS=-I/path with space"
which is parsed as a parameter named«CPPFLAGS» having the value«-I/path with space» (brackets added for clarity).
Using quotes is interesting. From (lightly) reading the bash man page I thought you had to escape the space with \, thus "/path with space" becomes /path\ with\ space I've never tried the quotes, but it seems that it doesn't work generally (your ls example). Escaping works with ls without quoting and without changing IFS.
What happens if you use the "escaping spaces" format of the command?
$ file="\"a b\""
$ eval ls $file
Everything depends on how the variable is used. First, note that if you are using Autoconf, this probably means that make will be used eventually, so that the rules are dictated by make, and in particular, the default make rules. Even though you may want to use your own rules exclusively, things must remain consistent between tools, and some variables have standard meanings, so that you do not want to deviate from them. This is not the case of CPPFLAGS, but this should remain similar to CFLAGS, which is standard. See the POSIX make utility, where variables are simply expanded with standard sh word splitting, which does not provide any quoting mechanism (the field separator is controlled by $IFS, but do not change the IFS variable to accept spaces as normal characters since this will break other things, like being able to provide several -I and/or -L options in such variables with the standard way).
Since there is such a limitation with make, I suppose that it would be useless to try to avoid this limitation in Autoconf.
Now, since a space is necessarily a field separator, the only possibility is to provide pathnames without space characters. If spaces in pathnames were to be supported in the future, this would probably be done via pathname encoding, with decoding at the high-level UI (a bit like with URL's). Alternatively, if you have the choice and really want to use spaces in pathnames, you may use some non-ASCII space (BTW, this is how RISC OS supports space in pathnames, by forcing it to be the no-break space).

Resources