Create one Makefile rule for several files - makefile

I would like to create just one Makefile rule instead of creating the same rule for different files of the same type.
Consider this small example where a files declare two Python-Notebooks and each of these Notebooks should be converted into a Python-Script (.py). This can be done with the command jupyter nbconvert --to script FILE. The Makefile I tried to create looks like this but is not working:
files = A.ipynb B.ipynb
all: convert
convert: $(files)
jupyter nbconvert --to script $< # How to define $< as the changed file?
Now, whenever A.ipynb is changed, I would like the command
jupyter nbconvert --to script A.ipynb
to be executed. Can this be done within one simple rule as 'suggested' above? I can write a Makefile accomplishing the task but its longer and scales with the size of files:
all: A.py B.py
A.py: A.ipynb
jupyter nbconvert --to script $<
B.py: B.ipynb
jupyter nbconvert --to script $<

This is what pattern rules are for:
all: A.py B.py
%.py: %.ipynb
jupyter nbconvert --to script $<
You can also use static pattern rules which some prefer.

Related

Make dependency rule does not execute

What I am trying to achieve: a make rule that would create the virtual environment for a script, activate it, and install package dependencies. (I've created a repo with files needed to recreate, for convenience).
Here is my Makefile:
venv:
#echo VENV
virtualenv $# -p python2
foo_requirements: requirements.txt venv .FORCE
#echo PIP
( . venv/bin/activate && pip install -r $< )
.PHONY: foo_requirements
FOO_CMD_SCRIPT = foo.py
FOO_CMD = . venv/bin/activate && python2 $(FOO_CMD_SCRIPT)
$(FOO_CMD_SCRIPT): foo_requirements
#--- Usage ---
all: $(FOO_CMD_SCRIPT)
$(FOO_CMD)
.FORCE:
The target all is there only for testing, in real life I would put the content in a foo.mk file, and include that from another makefile.
What I expect:
make all looks at the dependency FOO_CMD_SCRIPT for (actually a filename to a file on disk). Dependency is the foo_requirements rule (PHONY)
rule foo_requirements has file dependency requirements.txt and venv. There is .FORCE too in here, because I don't know how to check if package installation is already done. So what I think should happen is: 1. nothing for dependency requirements.txt (file exists, no rule) 2. run the rule for venv if it does not exist.
when venv rule has run and the directory is created, run the actual content of the rule: pip install.
after that, the dependencies for all should be finished, and the actual commands should run.
What actually happens:
venv gets created alright
pip never runs
the actual command never runs
Why doesn't the content of the rule foo_requirements run?
Likewise, the all rule content never runs.
Result:
$ make
VENV
virtualenv venv -p python2
created virtual environment CPython2.7.18.final.0-64 in 46ms
creator CPython2Posix(dest=/home/gauthier/tmp/test_mk/venv, clear=False, no_vcs_ignore=False, global=False)
seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/gauthier/.local/share/virtualenv)
added seed packages: pip==20.3.4, pkg_resources==0.0.0, setuptools==44.1.1, wheel==0.34.2
activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator
If you don't tell it otherwise, make will always build the first target in the makefile (along with any of its prerequisites) and then stop.
The first target in your makefile is venv and it has no prerequisites, so that target is built then make stops.
You can run make <target> to run a specific target, for example make all.
Or you can put the all target as the first one in the makefile.
Or you can add .DEFAULT_GOAL: all in your makefile.
See How make Processes a Makefile

Makefile repeatedly making .PHONY target

I have a Makefile which looks like this:
.PHONY: aws-deps
requirements.txt: Pipfile Pipfile.lock
pipenv lock -r > $#
aws-deps: requirements.txt
pip3 install --upgrade --target aws_src/ -r $<
If I run make requirements.txt more than once, it correctly says it's up to date. But if I run make aws-deps it doesn't behave as I expect a .PHONY target to, it runs every time regardless of whether requirements.txt has changed. For example, deleting requirements.txt first:
$ make aws-deps
pipenv lock -r > requirements.txt
pip3 install --upgrade --target aws_src/ -r requirements.txt
<snip>
$ make aws-deps
pip3 install --upgrade --target aws_src/ -r requirements.txt
<snip>
Am I mis-understanding what .PHONY deps do? I want aws-deps to only do something if its prerequisite has changed, ie I have a change in requirements.txt - does anybody know what I'm missing in getting that to work?
Thanks!
.PHONY targets tell make to treat a target as not being a file, even though there might be a file that has a name identical to this target. As there is no file named aws-deps here, .PHONY has no real influence in your case. Instead, make has nothing to compare the timestamp of requirements.txt to and assumes that the rule for aws-deps must be run. You might change this behavior by
AWS_DEP = .aws-deps-done # hidden file to compare a timestamp against
.PHONY: aws-deps
aws-deps: $(AWS_DEP)
$(AWS_DEP): requirements.txt
pip3 install --upgrade --target aws_src/ -r $<
#touch $#
I went through similar case. I wasn't too much of a fan of creating another file .hidden or visible. But that is what I've seen a lot around. I went to the GNU make manual [who does that ANYMORE?], hoping that the authors had considered something so obvious. I found the target .INTERMEDIATE, which is not expecting a file update. So, your example would be then:
requirements.txt: Pipfile Pipfile.lock
pipenv lock -r > $#
.INTERMEDIATE: aws-deps
aws-deps: requirements.txt
pip3 install --upgrade --target aws_src/ -r $<
It works well and does not require writing an extra file as a flag. I used this .INTERMEDIATE target type to print a message before a mass compilation of PDF files and another message for similar mass compilation of PNG files. If you try to use .PHONY the compilation will repeat. If you print the message inside the rules block, it will print for every file that is being processed. Printing a one-time message is another use of .INTERMEDIATE.

Calling CMake Makefile with another Makefile

I am attempting to call the auto-generated CMake makefile from outside of the CMake directory using make.
In order to accomplish this, I have made another makefile in the parent directory of my project. This file should cd into the CMake directory and call the makefile contained in there.
Unfortunately, I am having issues with the external makefile. The contents are as follows:
clean:
cd cmake-build-debug && $(MAKE) clean
I have a tab following the final line, but am still getting a separation error.
From the make man-page:
-C dir, --directory=dir
Change to directory dir before reading the makefiles or doing
anything else. If multiple -C options are specified,
each is interpreted relative to the previous one:
-C / -C etc is equivalent to -C /etc. This is typically
used with recursive invocations of make.
So, your clean rule can be:
clean:
$(MAKE) -C cmake-build-debug clean

Issues with wildcard (*) in Makefile

I am trying to symbolically link multiple files using my Makefile using the command: ln -s $(PWD)/bin/* ../../../bin/destination
If I run the command in native bash it works fine, but run in the Makefile it simply creates an * in the destination directory.
Any help would be appreciated.
You could use $(wildcard $(PWD)/bin/*) instead of $(PWD)/bin/* (assuming you are using GNU make; read it about wildcard pitfalls) and about the wildcard function
To debug the issue, I would suggest using remake (as remake -x) and/or make --trace

Command line arguments to make for working in Linux and Windows

I have gone through the link
Passing additional variables from command line to make.
I have a project which compiles both on Linux and Windows using makefiles. In Windows it uses gcc while in Linux it uses the ARM version of gcc ie armv7-linux-gcc.
I would like to use a command line variable which tells the makefile which compiler to use depending on Windows or Linux.
For example in Windows it should have something like this:
CC= gcc
CFLAGS= -c -D COMPILE_FOR_WINDOWS
and for Linux:
CC = armv7-linux-gcc
CFLAGS = -c -D COMPILE_FOR_LINUX
These preprocessor defines COMPILE_FOR_WINDOWS and COMPILE_FOR_LINUX are present in the code base and can't be changed.
Also for make clean it should clean up both for Windows and Linux. I can't assume that I people who build this will have Cygwin installed so can't use rm for deleting files.
This answer is only valid if you're using GNU make or similar:
Conditionally set your make variables using an Environment Variable.
For the 'clean' rule to function properly, you may also have to create a make variable for any differences in file extensions for either OS.
Naive example:
ifeq ($(OS), Windows_NT)
CC=gcc
RM=del
EXE=.exe
CFLAGS=-c -DCOMPILE_FOR_WINDOWS
else
CC=armv7-linux-gcc
RM=rm
EXE=
CFLAGS=-c -DCOMPILE_FOR_LINUX
endif
PROG=thing$(EXE)
.PHONY: all
all: $(PROG)
$(CC) $(CFLAGS) -o $(PROG) main.c
.PHONY: clean
clean:
-$(RM) $(PROG) *.o
Maybe you could use ant (with a build.xml file) to build the project.
Else, in a Makefile, you would need to check the system and put some conditions to check wether you are making the project in an Unix environment or a Windows environment.

Resources