I'm often creating png files out of dot (graphviz format) files. The command to do so is the following:
$ dot my_graph.dot -o my_graph.png -Tpng
However, I would like to be able to have a shorter command format like $ make my_graph.dot to automatically generate my png file.
For the moment, I'm using a Makefile in which I've defined the following rule, but the recipe is only available in the directory containing the Makefile
%.eps: %.dot
dot $< -o $# -Teps
Is it possible to define custom implicit GNU Make recipes ? Which would allow the above recipe to be available system-wide
If not, what solution do you use to solve those kind of problem ?
Setup:
Fedora Linux with ZSH/Bash
You could define shell functions in your shell's startup files, e.g.
dotpng()
{
echo dot ${1%.dot}.dot -o ${1%.dot}.png -Tpng;
}
This function can be called like
dotpng my_graph.dot
or
dotpng my_graph
The code ${1%.dot}.dot strips .dot from the file name if present and appends it (again) to allow both my_graph.dot and my_graph as function argument.
Is it possible to define custom implicit GNU Make recipes ?
Not without modifying the source code of GNU Make.
If not, what solution do you use to solve those kind of problem ?
I wouldn't be a fan o modyfying the system globally, but you could do:
Create a file /usr/local/lib/make/myimplicitrules.make with the content
%.eps: %.dot
dot $< -o $# -Teps
Use include /usr/local/lib/make/myimplicitrules.make in your Makefile.
I would rather use a git submodule or similar to share common configuration between projects, rather than depending on global configuration. Depending on global environment will make your program hard to test and non-portable.
I would rather go with a shell function, something along:
mymake() {
make -f <(cat <<'EOF'
%.eps: %.dot
dot $< -o $# -Teps
EOF
) "$#"
}
mymake my_graph.dot
GNU Make lets you specify extra makefiles to read using the MAKEFILES
environment variable. Quoting from info '(make)MAKEFILES Variable':
the default goal is never taken from one of these makefiles (or any
makefile included by them) and it is not an error if the files listed
in 'MAKEFILES' are not found
if you are running 'make' without a specific makefile, a makefile
in 'MAKEFILES' can do useful things to help the built-in implicit
rules work better
As an example, with no makefile in the current directory and the
following .mk files in make's include path (e.g. via
MAKEFLAGS=--include-dir="$HOME"/.local/lib/make/) you can create
subdir gen/ and convert my_graph.dot or dot/my_graph.dot by
running:
MAKEFILES=dot.mk make gen/my_graph.png
To further save some typing it's tempting to add MAKEFILES=dot.mk
to a session environment but defining MAKEFILES in startup files
can make things completely nontransparent. For that reason I prefer
seeing MAKEFILES=… on the command line.
File: dot.mk
include common.mk
genDir ?= gen/
dotDir ?= dot/
dotFlags ?= $(if $(DEBUG),-v)
Tvariant ?= :cairo:cairo
vpath %.dot $(dotDir)
$(genDir)%.png $(genDir)%.svg $(genDir)%.eps : %.dot | $(genDir).
dot $(dotFlags) $< -o $# -T'$(patsubst .%,%,$(suffix $#))$(Tvariant)'
The included common.mk is where you'd store general definitions to
manage directory creation, diagnostics etc., e.g.
.PRECIOUS: %/. ## preempt 'unlink: ...: Is a directory'
%/. : ; $(if $(wildcard $#),,mkdir -p -- $(#D))
References:
?= = := … - info '(make)Reading Makefiles'
vpath - info '(make)Selective Search'
order-only prerequisites (e.g. | $(genDir).) - info '(make)Prerequisite Types'
.PRECIOUS - info '(make)Chained Rules'
Related
When it comes to loading environment variables in a Makefile most answers point to doing this:
include .env.dev
export $(shell sed 's/=.*//' .env.dev)
where .env.dev is a file containing my environment variables, for example:
FOO=bar
SQL_DATABASE=mydb_dev
However, I have two .env files, one for production and one for development. It does not seem possible to place this include command inside a make command, nor a variable. What I would ideally like is to have a Makefile along the lines of:
include config/.env.django.dev
export $(shell sed 's/=.*//' config/.env.django.dev)
serve-dev:
- npm run dev
build-prod:
- npm run build
where running make serve-dev will first load in variables from the file .env.dev, but running make build-prod will first load in variables from the file .env.prod. Many of the variables have the same key but different values, so I can't load in both together.
What would be a good approach here?
I see two options:
Put the variable-loading code inside of a recipe, wrapped in $(eval ...).
The downside is that you won't be able to access those variables before entering a recipe, if you need them there for some reason.
serve-dev:
$(eval include include .env.dev)
$(eval export $(shell sed 's/=.*//' .env.dev))
Wrap the variable-loading code in something like ifneq ($(filter serve-dev,$(MAKECMDGOALS)),) ... endif.
This will make the variables accessible anywhere, put it will only work if the target was specified with a command-line flag, as opposed to being built as a dependency of some other target.
ifneq ($(filter serve-dev,$(MAKECMDGOALS)),)
include config/.env.django.dev
export $(shell sed 's/=.*//' config/.env.django.dev)
endif
I have a makefile in a directory of mine which builds scripts with certain environment variables set. What if I want to create another makefile in the same directory with different environment variables set? How should I name the two make files? Does makefile.1 and makefile.2 work? How do I call them?
You can give sensible names to the files like makefile.win and makefile.nix and use them:
make -f makefile.win
make -f makefile.nix
or have a Makefile that contains:
win:
make -f makefile.win
nix:
make -f makefile.nix
and use make win or make nix
You can name makefile whatever you want. I usually name it like somename.mk. To use it later you need to tell make what makefile you want. Use -f option for this:
make -f somename.mk
Actually you can have two set of environment variables in the same make file. for example
COMPILER = gcc
CCFLAGS1 = -g
CCFLAGS2 = -Wall
a: main.c
${COMPILER} ${CCFLAGS1} main.c
b: test.c
${COMPILER} ${CCFLAGS2} test.c
then you can just say make a or make b. Depending on what you want.
Also it is possible with -f flag to call which makefile you want to call.
You can do something like this rather than using multiple makefiles for the same purpose. You can pass the environment or set a flag to the same makefile. For eg:
ifeq ($(ENV),ENV1)
ENV_VAR = THIS
else
ENV_VAR = THAT
endif
default : test
.PHONY : test
test:
#echo $(ENV_VAR)
Then you can simply run the make command with arguments
make ENV=ENV1
I have two makefiles in the same directory. Many of the recipes have identical names and here are two solutions:
1. Prefix in make
proja_hello:
#echo "hello A"
projb_hello:
#echo "hello N"
2. Keep two separate files
Project A has makefile. Type make hello.
Project B has a separate make file called projb.mk. Type bmake hello.
This works since I've added alias bmake ='make -f projb.mk to my .bashrc. Note! This command can be called anywhere but only works where projb.mk exists.
Note! You lose autocompletion of make with the alias and typing make -f projb.mk hello is not better than typing make projb_hello.
How to add Qt resources in GNU makefile?
I want to add something like this:
mystyle.qrc
<RCC>
<qresource prefix="/">
<file>mystyle.qss</file>
</qresource>
</RCC>
And it should be used as here:
MyMain.cpp
QFile file(":/mystyle.qss");
A simple rule might look something like...
# Specify the `rcc' executable -- `rcc-qt5' on my box but
# may just be `rcc' elsewhere.
#
RCC := rcc-qt5
# Use rcc to generate a .qrc.cpp output file base on the input .qrc
#
%.qrc.cpp: %.qrc
$(RCC) -name $* -o $# $<
And then just use the generated .qrc.cpp as you would any other .cpp file. So if your main source file is mp_prog.cpp you could have...
my_prog: my_prog.o mystyle.qrc.o
$(LD) $(LDFLAGS) -o $# $+
Assuming the usual builtin rules mystyle.qrc.o will be built from mystyle.qrc.cpp which will, in turn, have been generated from mystyle.qrc using the new rule.
This is my first question on Stackoverflow so forgive me if I ask anything ridiculous :D.
Problem:
Suppose I want to compile a program that is in the directory "my dir/" with a space in it. Say the pathname of the program is "my dir/test.c".
Here is the sample makefile that I was trying out:
CC = gcc
DIR = my\ dir
$(DIR)/test.out: $(DIR)/test.c
# $(CC) $< -o $#
$(CC) $(DIR)/test.c -o $(DIR)/test.out
As you can see that in the last line(line-5) I have written the pathnames of the source and the output files directly as written in the prerequisite and the target, respectively. Doing this works fine because it yields the command:gcc my\ dir/test.c -o my\ dir/test.outwhich a syntactically correct way of passing filenames(with spaces) to gcc or any other shell command.
The second last line(line-4) is where the problem is(commented line). I've used automatic variables $# (Target) and $< (First and the only Prerequisite) to produce the filename arguments for gcc which I expected to bemy\ dir/test.out and my\ dir/test.c, respectively. But here, for some reason, the produced filenames are my dir/test.out and my dir/test.c and hence the yielded command is: gcc my dir/test.c -o my dir/test.out
Now here, gcc considers my and dir/test.c as different two different input filenames and the command generates errors.
Here is a screenshot of the generated error output when I uncomment line-4 and comment line-5 of the above Makefile:
My Question:
Is there any way to retain those backslashes even by using automatic variables the way I did? Or is there any alternative that will achieve the same goal as using automatic variables and also solve my problem? Because flexibility is important here.
Thanks in advance for your help!!!
Use double or single quotes for the automatic variables.
Use single quotes, if you want to avoid shell expansion of the values referenced by the automatic variables:
$(DIR)/test.out: $(DIR)/test.c
$(CC) '$<' -o '$#'
Double quotes allow shell expansion. For example, if there was a dollar sign in DIR:
DIR := $$my\ dir
then "$#" would expand to "$my dir", and the shell would interpret $my as variable.
SO
I can't find out why these lines are not called for the first time I run 'make' but are called the next time:
sb_path = sb
sb_src := $(sb_path)/src
sb_build := $(sb_path)/build
ifndef DO_NOT_GENERATE_COMMIT_INFO
commit_sb: | $(sb_bin)
#$(sb_build)/generate-commit-info $(sb_path)
$(sb_src)/last_git_commit_info.h: | commit_sb ;
endif
I'm just curious because there is no file generate-commit-info file and make crashes when I call it for the second time, but it compiles my program ok for the first try.
I use script on my local machine to copy sources over ssh to another machine and to run compile.sh script there:
...
scp -r $sbfolder/build $sbfolder/Makefile "$buildserver:$root/$curdate"
check_retcode
scp -r $sbfolder/sb/Makefile "$buildserver:$root/$curdate/sb/"
...
ssh $buildserver "$root/compile.sh $curdate $debug"
compile.sh:
# fix Makefile: we don't have git installed here
#DO_NOT_GENERATE_COMMIT_INFO=true
#now we can compile sb
curdir="/home/tmp/kamyshev/sb_new/$1"
cd $curdir
check_retcode
t_path=$curdir
debug=$2
config=RELEASE
if [[ debug -eq 1 ]]; then
config=DEBUG
fi
echo "building sb... CONFIG=$config"
make -j2 CONFIG=$config
check_retcode
As you see DO_NOT_GENERATE_COMMIT_INFO=true is commented out. So I just don't see a reason why the code is not run when I call a make or the script for the first time (either from the remote script or myselft from command line).
Do you have any clues?
UPDATE on Etan Reisner comment:
commit_sb target is checked, it does not exist, so it's rule is being run and it updates last_git_commit_info.h. Thus it forces to update the .h file. It also gives me a .PHONY target commit_sb so I could do it directly by calling make commit_sb.
The generate-commit-info also creates a file in a $(sb_bin) folder.
My another guess is that you are talking about a better way to organize this code.
I can update last_git_commit_info.h directly with a such rule:
commit_sb $(sb_src)/last_git_commit_info.h: FORCE | $(sb_bin)
#$(sb_build)/generate-commit-info $(sb_path)
FORCE:
Thanks to the commenters on my question I've done some additional research: I've tried to make a minimal complete example. And this led me to the answer.
My code generates dependency files (look at -MMD command in SB_CXXFLAGS):
# just example - in real Makefile these are calculated on the fly
sb_deps := file1.d file2.d [...]
# rules with dependances of .o files against .h files
-include $(sb_deps)
SB_CXXFLAGS = $(CXXFLAGS) [...] -MMD
# compile and generate dependency info;
$(sb_obj)/%.o:$(sb_src)/%.cpp
$(CXX) $(SB_CXXFLAGS) $< -o $#
And when I run make for the first time there no *.d files, so no *.cpp depends on last_git_commit_info.h file and the rule is not applied.
On the subsequent runs the dependency rule appears in one of *.d files, the rule is executed and I get the error.
UPDATE: This does not concern the question directly, but this is the better way to write these rules:
ifndef DO_NOT_GENERATE_COMMIT_INFO
commit_sb $(sb_src)/last_git_commit_info.h: FORCE | $(sb_bin)
#$(sb_build)/generate-commit-info $(sb_path)
FORCE:
endif