Let's say my Makefile is something like:
DIR :=#
foobar:
ls ${DIR}
When i type
mak[tab] f[tab]
it gives correctly
make foobar
But
make foobar D[tab]
doesn't do the magic
make foobar DIR=
So my question is: is there a way to autocomplete Makefile variables too (aside targets) in bash?
This answer is far from complete. To grep all variables in a Makefile we use make -p to print the Makefile database:
# GNU Make 3.81
# Copyright (C) 2006 Free Software Foundation, Inc.
# This is free software; see the source for copying conditions.
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
# This program built for x86_64-pc-linux-gnu
# Make data base, printed on Mon Oct 13 13:36:12 2014
# Variables
# automatic
<D = $(patsubst %/,%,$(dir $<))
# automatic
?F = $(notdir $?)
# environment
DESKTOP_SESSION = kde-plasma
# ...
# makefile (from `Makefile', line 1)
DIR :=
We are looking for lines starting with # makefile (from 'Makefile', line xy) and extract the name of the following variable:
$ make -p | sed -n '/# makefile (from/ {n; p;}'
MAKEFILE_LIST := Makefile
DIR :=
In the next step we remove everything but the variable name (everything after :=):
$ make -p Makefile | sed -n '/# makefile (from/ {n; s/^\([^.#:= ]\+\) *:\?=.*$/\1/p;}'
MAKEFILE_LIST
DIR
The following lines demonstrate how it could be done:
_make_variables()
{
# get current completion
local cur=${COMP_WORDS[COMP_CWORD]}
# get list of possible makefile variables
local var=$(make -p Makefile | sed -n '/# makefile (from/ {n; s/^\([^.#:= ]\+\) *:\?=.*$/\1=/p;}')
# don't add a space after completion
compopt -o nospace
# find possible matches
COMPREPLY=( $(compgen -W "${var}" -- ${cur}) )
}
# use _make_variables to complete make arguments
complete -F _make_variables make
Now make D[tab] results in make DIR=.
Sadly you will loose all the file and target completion with this approach. Also it would be useful to remove some more variables (e.g. MAKEFILE_LIST) from the completion output.
Maybe it is worth to fill a wish/bug report against the bash-completion project to add this feature.
Related
This question already has answers here:
Define make variable at rule execution time
(4 answers)
Closed 4 years ago.
How can one use the variable defined inside a make target
.PHONY: foo
VAR_GLOBAL=$(shell cat /tmp/global)
foo:
echo "local" > /tmp/local
VAR_LOCAL=$(shell cat /tmp/local)
echo ${VAR_GLOBAL}
echo ${VAR_LOCAL}
here is the execution output:
$ echo global > /tmp/global
$ make foo
echo "local" > /tmp/local
VAR_LOCAL=local
echo global
global
echo
As #KelvinSherlock pointed out this is a duplicate of another question
here is the specific solution for my question:
.PHONY: foo
VAR_GLOBAL=$(shell cat /tmp/global)
foo:
echo "local" > /tmp/local
$(eval VAR_LOCAL := $(shell cat /tmp/local))
echo ${VAR_GLOBAL}
echo ${VAR_LOCAL}
You probably want to use the override directive in a target-specific variable assignment, so try:
foo: override LS_LOCAL=$(shell ls /var | tail -1)
echo ${LS_GLOBAL}
echo ${LS_LOCAL}
If LS_LOCAL is never defined (even by builtin-rules) you might not need the override keyword.
BTW, you might avoid $(shell ls /var | tail -1) by using the wildcard function combined with the lastword function (perhaps combined with notdir function), so you might code $(lastword $(wildcard /var/*)) or $(notdir $(lastword $(wildcard /var/*))) instead . However, beware of the order of expansion, and of filenames with spaces. At last the shell function probably uses your $PATH variable (so strange things could happen if some weird ls program appears there before /bin/ls). Perhaps using $(shell /bin/ls /var | /usr/bin/tail -1) might be better.
Look also into Guile-extended make; consider perhaps some other build-automation tool like ninja and/or generating your Makefile (or other build configuration) with something like a configure script generated via autoconf or cmake.
Notice also that a command in recipe can be made of several physical backslashed lines (hence executed in the same shell). Maybe you might consider something like
export MY_VAR=$$(ls /var | tail); \
dosomething; \
use $$MY_VAR
inside some recipe.
I'm trying to define an Automake rule that will generate a text file containing the full path to a libtool library that will be built and installed by the same Makefile. Is there a straightforward way of retrieving the output filename for a libtool library (with the correct extension for the platform the program is being built on)?
For example, I am trying to write something like this:
lib_LTLIBRARIES = libfoo.la
bar.txt:
echo $(prefix)/lib/$(libfoo_la) >$#
Where $(libfoo_la) would expand to libfoo.so, libfoo.dylib or libfoo.dll (or whatever else), depending on the platform. This is essentially the value of the dlname parameter in the resulting libtool library file. I could potentially extract the filename directly from that, but I was hoping there was a simpler way of achieving this.
Unfortunately, there's not a way I've found of doing this.
Fortunately, for you, I did have a little sed script hacked together that did kind
of what you want, and hacked it so it does do what you want.
foo.sed
# kill non-dlname lines
/^\(dlname\|libdir\)=/! { d }
/^dlname=/ {
# kill leading/trailing junk
s/^dlname='//
# kill from the last quote to the end
s/'.*$//
# kill blank lines
/./!d
# write out the lib on its own line
s/.*/\/&\n/g
# kill the EOL
s/\n$//
# hold it
h
}
/^libdir=/ {
# kill leading/trailing junk
s/^libdir='//
# kill from the last quote to the end
s/'.*$//
# paste
G
# kill the EOL
s/\n//
p
}
Makefile.am
lib_LTLIBRARIES = libfoo.la
bar.txt: libfoo.la foo.sed
sed -n -f foo.sed $< > $#
I have a file, tmp containing:
{
"VolumeId": "vol--22222222",
}
and a makefile, containing only:
MYFILE =latest
x:
\cp tmp $(MYFILE)
grep VolumeId $(MYFILE)
#echo $(shell grep VolumeId $(MYFILE))
If I run make x, I get:
\cp tmp latest
grep VolumeId latest
"VolumeId": "vol--22222222",
VolumeId: vol--22222222,
as expected. If I modify the file tmp, replacing 2's wit 4's, I get:
\cp tmp latest
grep VolumeId latest
"VolumeId": "vol--44444444444",
VolumeId: vol--22222222,
...huh? The greps return different results! The second contains data from the file before the copy.
I run
rm latest ; make x
I get:
\cp tmp latest
grep VolumeId latest
"VolumeId": "vol--4444444444",
What's going on here?
GNU Make 3.81. Ubuntu 12.04 on VMWare 5.
Update 1
Here's a more explicit example
CMD0 =$(shell date +"%s.%N")
CMD1 =date +"%s.%N"
CMD2 =date +"%s.%N"
y:
#sleep 2
#date +"%s.%N" # 2nd
#echo $(CMD0) # 1st A
#sleep 2
#date +"%s.%N" # 3rd
#sleep 2
#$(CMD1) # 4th
#sleep 2
#echo $(shell $(CMD2) ) # 1st B
Output:
1381596581.761093768
1381596579.743610973
1381596583.769058027
1381596585.774766561
1381596579.751625601
Its look like all the $(shell ... ) commands get evaluated together before any line of the y recipe.
This is a little surprising (and counter-intuitive - what's the rationale for this odd design?). It has important consequences if the shell commands have side effects. Also, I can't find any documentation in make manual that describes this order of evaluation.
Update 2
What's especially odd about the above is that its hard to come up with a mental model for the order of evaluation. Is it that any rule of the form, $(function ... ) is executed before each line of the recipe? If so, why is $(CMD0) evaluated before the recipe, but not $(CMD1). Its true that CMD0 contains a $(f ... ) - does make look at this, even though CMD0 is declared as a delayed evaluated variable (i.e.declared with = not :=)?
Update 3
Reducing it down to essential components:
notSafe:
backup-everything # Format executed even if backup fails
echo $(shell format-disk) | tee log.txt #
CMDX =$(shell format-disk)
alsoNotSafe:
backup-everything # Format executed even if backup fails
echo $(CMDX) | tee log.txt # Even though CMDX is a delayed evaluation variable
CMDZ =format-disk
safe:
backup-everything # Works as expected.
$(CMDZ) | tee log.txt # Evaluation of CMDZ is delayed until line is executed.
Mad. Who designed this?
You're not telling us everything: when you run it the 1st time, you echo an empty line. Instead you get a grep file not found error.
And that's the decisive hint: $-expressions are evaluated by any variant of make before running the commands of the rule. I.e. in each later run you see the file contents of the previous run.
Btw. your leading backslash is spurious and causes the command to be passed to the Shell, instead of being directly executed by make. With makepp's builtin commands (prefixed by &) and fixed dependencies that would be:
latest: tmp
&cp $(input) $(output)
&grep VolumeId $(output)
#&echo $(&grep VolumeId $(output))
That doesn't change the problem, but by using the proper rule variables, makes it obvious what's wrong.
Considering that every command is run in its own shell, what is the best way to run a multi-line bash command in a makefile? For example, like this:
for i in `find`
do
all="$all $i"
done
gcc $all
You can use backslash for line continuation. However note that the shell receives the whole command concatenated into a single line, so you also need to terminate some of the lines with a semicolon:
foo:
for i in `find`; \
do \
all="$$all $$i"; \
done; \
gcc $$all
But if you just want to take the whole list returned by the find invocation and pass it to gcc, you actually don't necessarily need a multiline command:
foo:
gcc `find`
Or, using a more shell-conventional $(command) approach (notice the $ escaping though):
foo:
gcc $$(find)
As indicated in the question, every sub-command is run in its own shell. This makes writing non-trivial shell scripts a little bit messy -- but it is possible! The solution is to consolidate your script into what make will consider a single sub-command (a single line).
Tips for writing shell scripts within makefiles:
Escape the script's use of $ by replacing with $$
Convert the script to work as a single line by inserting ; between commands
If you want to write the script on multiple lines, escape end-of-line with \
Optionally start with set -e to match make's provision to abort on sub-command failure
This is totally optional, but you could bracket the script with () or {} to emphasize the cohesiveness of a multiple line sequence -- that this is not a typical makefile command sequence
Here's an example inspired by the OP:
mytarget:
{ \
set -e ;\
msg="header:" ;\
for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
msg="$$msg :footer" ;\
echo msg=$$msg ;\
}
The ONESHELL directive allows to write multiple line recipes to be executed in the same shell invocation.
all: foo
SOURCE_FILES = $(shell find . -name '*.c')
.ONESHELL:
foo: ${SOURCE_FILES}
FILES=()
for F in $^; do
FILES+=($${F})
done
gcc "$${FILES[#]}" -o $#
There is a drawback though : special prefix characters (‘#’, ‘-’, and ‘+’) are interpreted differently.
https://www.gnu.org/software/make/manual/html_node/One-Shell.html
Of course, the proper way to write a Makefile is to actually document which targets depend on which sources. In the trivial case, the proposed solution will make foo depend on itself, but of course, make is smart enough to drop a circular dependency. But if you add a temporary file to your directory, it will "magically" become part of the dependency chain. Better to create an explicit list of dependencies once and for all, perhaps via a script.
GNU make knows how to run gcc to produce an executable out of a set of .c and .h files, so maybe all you really need amounts to
foo: $(wildcard *.h) $(wildcard *.c)
What's wrong with just invoking the commands?
foo:
echo line1
echo line2
....
And for your second question, you need to escape the $ by using $$ instead, i.e. bash -c '... echo $$a ...'.
EDIT: Your example could be rewritten to a single line script like this:
gcc $(for i in `find`; do echo $i; done)
I need the xpi_hash variable to be assigned only when update target's command is decided to execute. Then I'm using this variable as environment, exporting, etc..
If I put it outside of rule, it will be expanded firstly, before $(xpi) target is called, hence will not find that file.
substitute := perl -p -e 's/#([^#]+)#/$$ENV{$$1} bla bla...
export xpi_hash
.PHONY: dirs substitute update
update: $(xpi) $(target_update_rdf)
xpi_hash := $(shell sha1sum $(xpi) | grep -Eow '^[^ ]+')
#echo "Updating..."
$(target_update_rdf): $(update_rdf)
$(substitute) $< > $#
and above of course is not correct, because for command part the shell is represented. So maybe another way to put this question is - how to bring variable as command output?
I'm not sure exactly what you're looking for here, how are you planning to use xpi_hash? If you want to get the current hash every time you use the variable use = to assign the variable instead of :=, e.g.
xpi_hash=$(shell sha1sum $(xpi) | grep -Eow '^[^ ]+')
update:$(xpi) $(target_update_rdf)
#echo $(xpi_hash)
will print the hash of xpi after it has been updated.
For variables in make, see section 6.2 of the manual. Briefly ':=' will expand variables on the right hand side, '=' will leave them to be expanded later.
The altered command in my comment (substitute = xpi_hash="$(xpi_hash)" perl -p -e 's/#([^#]+)#/$$ENV{$$1}...') will expand to be equivalent to
$(substitute)
xpi_hash="$(xpi_hash)" perl -p -e 's/#([^#]+)#/$$ENV{$$1}...'
xpi_hash="`sha1sum $(xpi) | grep -Eow '^[^ ]+'`" perl -p -e 's/#([^#]+)#/$$ENV{$$1}...'
xpi_hash="`sha1sum xpi_expansion | grep -Eow '^[^ ]+'`" perl -p -e 's/#([^#]+)#/$$ENV{$$1}...'
The xpi_hash="..." syntax is defining a variable in the bash subshell, rather than using the variable in make.
If only substitute must use xpi_hash, make xpi_hash a target-specific variable:
$(target_update_rdf): xpi_hash = $(shell ...)
$(target_update_rdf): $(update_rdf)
$(substitute) $< > $#
If other Perl scripts will need xpi_hash, and you want to export it, you have a problem, because the variables assigned in the subshells of a rule cannot (easily) be communicated to Make. But you can store it in a file and include it:
xpi_hash_file: $(xpi)
rm -f $#
echo xpi_hash = $(shell...) > $#
-include xpi_hash_file