How to validate a requirement/dependency in a Makefile? - makefile

I have the following setup:
/-
.project-name
Makefile
...
The content of .project-name is dummy
cat .project-name
dummy
One of the targets of the Makefile is set_project_name which (as its name suggests) changes (using sed) the name of the project all around the directory and adjusting some paths too
set_project_name:
#ag [dD]ummy -l0 | xargs -0 sed -i "s/[dD]ummy/${PROJECT_NAME}/g"
## Renombrar la carpeta del proyecto
#[[ -d dummy ]] && mv dummy/pipelines/dummy.py dummy/pipelines/$(PROJECT_NAME).py
#[[ -d dummy ]] && mv dummy $(PROJECT_NAME)
also in the Makefile the variable PROJECT_NAME is just
PROJECT_NAME := `cat .project-name`
I want to validate that the user of this changed the content of .project-name before executing anything in the Makefile
I tried using
TEST_PROJECT_NAME_2 := [[ 'dummy' == $(cat .project-name) ]] && $(error "ERROR: The name of the project can not be 'dummy', please use another name")
But is not working, it executes, but always throws the error.
I tried also with ifeq at the top of the Makefile but that is not executing neither. What I am doing wrong? Also, Is this the best approach for doing it?

The language make uses is not the same that the shell uses. Things like
PROJECT_NAME := `cat .project-name`
will not make PROJECT_NAME the contents of the .project-name file (it will assign the string "`cat .project-name`". Likewise,
TEST_PROJECT_NAME_2 := [[ 'dummy' == $(cat .project-name) ]] ...
will not do what you think it does. To assign the standard output of a shell command, you need to use make syntax such as
FOOFILE := $(shell cat foo)

Related

Interactive input of a Makefile variable

I want to run makefile with input variable. What I want is that if I write down the project name, a folder with that name will be created.
So I write read command:
CC = gcc
CFLAGS = -W -Wall
FILE := hi
src = $(wildcard *.c)
OBJ = $(src:.c=.o)
all : $(FILE)
$(FILE) : $(OBJ)
$(CC) $(CFLAGS) -o $# $^
.PHONY: clean
clean :
rm *.o $(FILE)
move :
mkdir -p ../../bin/$(FILE);
mkdir -p ../../exe/$(FILE);
mv *.o ../../bin/$(FILE);
mv $(FILE) ../../exe/$(FILE)
afterclean :
rm ../../bin/$(FILE)/*.o;
rm ../../exe/$(FILE)/$(FILE)
execute :
./../../exe/$(FILE)/$(FILE)
read :
#read -p "Enter : " enter; \
$(FILE) := enter; \
echo $FILE
What I wanna do is if I get FILE name through read I want to change FILE variable, but I can't change it. How can I do that?
Well in short, you cannot easily do that (and you should likely not want to, scroll down for rationale). If you have a closer look at your Makefile you'd notice that you're mixing make and shell syntax... and their contexts.
In your case, it literally passes the following string to shell (value of SHELL, likely defaults to /bin/sh) with -c:
read -p "Enter : " enter; \
hi := enter; \
echo ILE
Which shows the effects of the intermixed syntax. $(FILE) (value hi) and $F (unset -> empty) are make variables substituted by make before invoking shell. (while read into enter variable is not used at all and instead literal string enter is used in attempted make variable assignment inside that running shell.)
If you wanted to run a shell command and assign a value from what it has done / learned to a make variable, you would have to do so using shell function (or generate a (temporary) file you would include, but that's even messier):
FILE := $(shell read -p "Enter: " enter ; echo $${enter})
That however always asks... unless you use conditional assignment (?=) in which case you could choose already from the command line (make FILE=something, at which point we're about to close the circle). I am generally unsure what your intentions were how to tell make when to ask and when to use default value of hi.
That leads me to why this notion sounds suspect to me to start with and why suggestion made by #HolyBlackCat is a superior way of customizing invocation of make.
Also any runtime user interactions generally break automation (which is what we have make for) and also make builds non-reproducible. So, they better are to be avoided.
In other words, if you really had to, I'd say write an interactive_make_call.sh around it for this type of invocation:
#!/bin/bash
read -p "Enter : " enter
make FILE="${enter}" "$#"
Or even:
#!/bin/bash
read -p "Enter : " enter
if [[ -n "${enter}" ]] ; then
make FILE="${enter}" "$#"
else
make "$#"
fi
To fallback on the default value of FILE from the Makefile if you just press enter.

how to read from stdin into a makefile variable

I want to write a Makefile to install go program and other assets, so I want a installdir. I want to check if GOBIN, GOPATH is set, if not, want user to enter a installdir.
I wrote the Makefile as following, but the makefile variable installdir is empty. echo output nothing.
installdir:=$(shell echo $(GOPATH) | cut -d':' -f1)
all: *.go
#GO111MODULE=on GOPATH=$(GOPATH) go build -o trpc main.go
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
Please help!
This code:
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
simply cannot work and represents a fundamental misunderstanding of how make operates.
Make works in two distinct stages: first, it reads and parses all the makefiles, included makefiles, etc. Second, it determines which targets are out of date and runs the recipes to update those targets. Recipes will start a shell and pass the recipe text to that shell. When the shell exits make determines whether it worked or not by looking at the exit code. All make variables and functions in the entire recipe are expanded first, the the shell is invoked on the results. Further, every logical line in the recipe is started in a different shell.
So in your makefile, the ifeq options (which are makefile constructs) are parsed during the first stage, as the makefile is read in. The recipe lines are not run until the second stage, so changes to the installdir variable in a recipe cannot impact the ifeq lines. Further, changes to installdir in a recipe cannot even be seen by make because they happen in a shell, then the shell exits and those changes are lost.
You'll have to write this entire thing in shell syntax and put all of it into a recipe, something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || read -s -p "Please input installdir: " installdir; \
echo $$installdir
(untested). You have to use shell constructs, not make constructs. You should virtually never use the $(shell ...) make function inside a recipe: a recipe is already running in a shell. And you have to use backslash/newline pairs to ensure make considers the entire recipe one logical line, else variables set on one line will not be set on the next line.
Finally, I should point out that this (reading input during make) is just generally a bad idea. For example, if you run make install with the -j option, only one recipe can have control of stdin and make will choose more-or-less randomly which it is.
Generally instead you want to have the user pass the value on the command line, with something like:
$ make installdir=my/dir
so your check in the makefile should instead be something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || { echo "Please add installdir=... on the command line"; exit 1; }; \
echo $$installdir

GNU Make - Set a flag if multiple files exist

In my Makefile, I check if a file exists, and execute different code depending on the result of that check.
Previously, there was only one relevant file, OUTLIB_CACHE.
OUTLIB_CACHE := filename
OUTLIB_CACHE_EXITS := $(shell if [ -f $(OUTLIB_CACHE) ]; then echo "1"; fi;)
ifneq ($(OUTLIB_CACHE_EXISTS),1)
# do something
endif
Now I am introducing a second relevant file, storing both of the file names in a list:
OUTLIB_CACHE_LIST := filename1 filename2
I want to set the OUTLIB_CACHE_EXISTS flag to true if and only if all the files in OUTLIB_CACHE_LIST exist. What would be the best way to do this?
You could substitute each element of OUTLIB_CACHE_LIST with a command, then execute the resulting commands in a shell:
OUTLIB_CACHE_MISSING := $(shell $(patsubst %,test -e "%" || echo missing;,$(OUTLIB_CACHE_LIST)))
If all the members exist, then the output of the shell command will be empty, else it will contain one word for each missing file. You can test for emptiness:
ifneq ($(OUTLIB_CACHE_MISSING),)
# do something
endif
If you want to know which files are missing, you can't just replace ! with %, because patsubst only replaces the first % it finds. Instead, you could use foreach:
OUTLIB_CACHE_MISSING := $(shell $(foreach f,$(OUTLIB_CACHE_LIST),test -e "$f" || echo "$f";))
Putting this all together in a testable example:
OUTLIB_CACHE_LIST := /etc /bin
#OUTLIB_CACHE_LIST += /nonexistent
OUTLIB_CACHE_MISSING := $(shell $(foreach f,$(OUTLIB_CACHE_LIST),test -e "$f" || echo "$f";))
.PHONY: all
all:
ifneq ($(OUTLIB_CACHE_MISSING),)
false "$(OUTLIB_CACHE_MISSING)"
else
true "No missing files"
endif
Uncomment the second line to select the first branch of the if.
N.B. You wanted to know whether the files exist, so I've used test -e rather than -f or -r. You'll know which test is actually appropriate for your case...
There is no need to use shell functions to test for the existence of multiple files in make. I use the following construct to make make output decent error messages for a very convoluted makefile. I presume you can adopt it to your needs if need be. The original idea (probably) comes from https://stackoverflow.com/a/20566812/1905491.
$(foreach p,$(ALL_INCLUDES),$(if $(wildcard $(p)),,$(info $(p) does not exist!) $(eval err:=yes)))
$(if $(err),$(error Aborting),)
Simply define OUTLIB_CACHE_EXISTS as follows
OUTLIB_CACHE_EXISTS := $(shell if ls $(OUTLIB_CACHE_LIST) >/dev/null 2>&1; then echo "1"; fi)
This should work
ifeq ($(OUTLIB_CACHE_LIST),$(wildcard $(OUTLIB_CACHE_LIST)))
# do something
endif

Modifying file extensions using Makefiles

I'm new to Makefiles and I want to modify the extension of a set of files. The following command works on the shell:
for file in path/*.ext1; do j=`echo $file | cut -d . -f 1`;j=$j".ext2";echo mv $file $j; done
However, I'm not sure how to run this in a Makefile. I tried running
$(shell for file in path/*.ext1; do j=`echo $file | cut -d . -f 1`;j=$j".ext2";echo mv $file $j; done)
But this never did what I needed it to do. What do I need to do to make this work on the Makefile? How do I call it in a section?
The immediate answer to your question is that the $ character is special to make: it introduces a make variable. If you want to pass a $ to the shell, you'll have to write two of them: $$.
So, your shell function invocation would have to be written as:
$(shell for file in path/*.ext1; do j=`echo $$file | cut -d . -f 1`;j=$$j".ext2";echo mv $$file $$j; done)
However, this is almost certainly not a good way to do what you want. You don't really describe clearly what you want to do, however. If you just want to have a target in a makefile that can be invoked to make this change, you can use:
fixext:
for file in path/*.ext1; do \
j=`echo $$file | cut -d . -f 1`; \
j=$$j".ext2"; \
echo mv $$file $$j; \
done
Or, taking advantage of some useful shell shortcuts, you could just run:
fixext:
for file in path/*.ext1; do \
echo mv $$file $${file%.*}.ext2; \
done
Now if you run make fixext it will perform those steps.
But, a much more make-like way to do it would be to write a single rule that knows how to rename one file, then use prerequisites to have them all renamed:
TARGETS = $(patsubst %.ext1,%.ext2,$(wildcard path/*.ext1))
fixext: $(TARGETS)
%.ext2 : %.ext1
mv $< $#
Now you can even run make -j5 and do 5 of the move commands in parallel...
you can also add rename blocks at the top of your file eg to change a suffix
output := $(input:.mov=.mp4)
but this won't work inside a make command as far as I can see
check:
output := $(input:.mov=.mp4)
gives
$ input=walkthrough.mov make check
output := walkthrough.mp4
make: output: No such file or directory
make: *** [check] Error 1

Use $RANDOM in a makefile

I am making a makefile to rename files with a random number in it (I am a newbie in shell script). I don't understand why, but when I run the file $rand is given the value 'ANDOM'. When I run this outside of the makefile it works.
I run this in the Mac os terminal, in case it's helpful.
all: renamefiles
renamefiles:
rand=$RANDOM && mv myfile.css $rand-myfile.css && mv myotherfile.css $rand-myotherfile.css
Wouldn't it be easier/better to use a date/time stamp so that the renamed files are listed in date order?
You need to use two $ signs in the makefile for each $ that you want the shell to see.
Thus:
all: renamefiles
renamefiles:
rand=$$RANDOM && \
mv myfile.css $$rand-myfile.css && \
mv myotherfile.css $$rand-myotherfile.css
Or, with date/time stamps:
all: renamefiles
renamefiles:
time=$$(date +'%Y%m%d-%H%M%S') && \
mv myfile.css $$time-myfile.css && \
mv myotherfile.css $$time-myotherfile.css
To use a random number within one or multiple make variables, the following works fine for me:
FOO="some string with \"$$rand\" in it"
BAR=" you may use it $$rand times."
foobar:
rand=$$$$ && \
echo $(FOO) $(BAR)
You might need to surround a multi-letter macro name with braces (or parentheses), for example
${RANDOM}
$(RANDOM)
ref

Resources