Simplify Makefile and maybe avoid using $(basename $#) is multiple places - makefile

I am new to Makefiles and it works as expected. But I am wondering is there a way to simplify this Makefile I wrote. More specifically I used $(basename $#).scala and $(basename $#) in many places and I think it is an anti-pattern. Or maybe is there a way to generalize it.
I appreciate any feedback or hint.
SPECS=First Follow
EXAMPLES_PATH=../..
ROOT_PATH=../${EXAMPLES_PATH}
SCALAV=2.12
APSLIB=${ROOT_PATH}/lib/aps-library-${SCALAV}.jar
SCALA_FLAGS=.:${APSLIB}
APS2SCALA=${ROOT_PATH}/bin/aps2scala
all: $(addsuffix Spec.compile, $(SPECS)) $(addsuffix Spec.run, $(SPECS))
%.generate:
${APS2SCALA} -DCOT -p ${EXAMPLES_PATH}:${ROOT_PATH}/base $*
%.run:
#scala -cp ${SCALA_FLAGS} $(basename $#)
GrammarUtil.compile: grammar.generate
scalac -cp ${SCALA_FLAGS} grammar.scala $(basename $#).scala
first.compile:
scalac -cp ${SCALA_FLAGS} $(basename $#).scala
follow.compile:
scalac -cp ${SCALA_FLAGS} $(basename $#).scala
Spec.compile:
scalac -cp ${SCALA_FLAGS} $(basename $#).scala
FirstSpec.compile: Spec.compile grammar.generate GrammarUtil.compile first.generate first.compile
scalac -cp ${SCALA_FLAGS} $(basename $#).scala
FollowSpec.compile: Spec.compile grammar.generate GrammarUtil.compile follow.generate follow.compile
scalac -cp ${SCALA_FLAGS} $(basename $#).scala
clean:
rm -f *.class grammar.scala first.scala follow.scala

The Make variable $* does roughly this; it returns whatever matched % in a pattern rule (called the stem).
Several of your recipes have exactly the same code, so there are multiple reasons to refactor to use a pattern rule instead. A complication as that one of the *.compile rules has a second dependency, but we can simply create a rule which leaves this part empty if there is only one dependency.
%.compile: %.scala
scalac -cp ${SCALA_FLAGS} $(filter-out $<,$^) $<
grammar.compile: grammar.generate
This also fixes the bug that it declares the formerly undeclared (but obvious) dependency on the %.scala file; thus, we use $< instead of $*.scala though in this case they are basically equivalent.
$(filter-out $<,$^) removes the first dependency (%.scala) from the list of all depencencies, yielding either nothing or grammar.generate.
However, your Makefile still appears to have several other undeclared dependencies, and inconsistent naming for some targets.
In particular, the above requires you to rename GrammarUtil to grammar; without more information about your inputs and their internal dependencies, I can't tell if that's really feasible.
Probably a less rocky road would be to keep exactly the same stem for all related files, and accept the default output file name (I'm guessing %.o? But I'm not familiar with Scala) instead of made-up and probably nonexistent pseudo-filenames like %.compile. (If you want to keep these, and they indeed do not correspond to actual file names, you should probably declare them as .PHONY; but then you are disabling useful and important make functionality.)

Related

Simplify Makefile without changing its logic

I am wondering is there a way to refactor these 3 lines without changing the logic?
scalac -cp ${SCALA_FLAGS} GrammarUtil.scala
scalac -cp ${SCALA_FLAGS} FirstSpec.scala
scalac -cp ${SCALA_FLAGS} FollowSpec.scala
Makefile
SPECS=FirstSpec FollowSpec
EXAMPLES_PATH=../..
ROOT_PATH=../${EXAMPLES_PATH}
SCALAV=2.12
APSLIB=${ROOT_PATH}/lib/aps-library-${SCALAV}.jar
SCALA_FLAGS=.:${APSLIB}
APS2SCALA=${ROOT_PATH}/bin/aps2scala
.PHONY: all
all: $(addsuffix .run, $(SPECS))
.PHONY: clean
clean:
rm -f *.class grammar.scala first.scala follow.scala
# for anything.scala it compiles it to create class file
%.class: %.scala
scalac -cp ${SCALA_FLAGS} $<
# for anything.scala that does not already exist, it generates it
%.scala:
${APS2SCALA} -DCOT -p ${EXAMPLES_PATH}:${ROOT_PATH}/base $*
# for anything.run it needs to be compiled first before running
%.run: %.class
scala -cp ${SCALA_FLAGS} $(basename $<)
# for GrammarUtil.class it needs to generate and compile grammar.class and then compile itself
GrammarUtil.class: grammar.class
scalac -cp ${SCALA_FLAGS} GrammarUtil.scala
# How to avoid above line: scalac -cp ${SCALA_FLAGS} GrammarUtil.scala
# for FirstSpec.class it needs to compile Spec.class, GrammarUtil.class and compile and then generate first.class
FirstSpec.class: Spec.class GrammarUtil.class first.class
scalac -cp ${SCALA_FLAGS} FirstSpec.scala
# How to avoid above line: scalac -cp ${SCALA_FLAGS} FirstSpec.scala
# for FollowSpec.class it needs to compile Spec.class, GrammarUtil.class and compile and then generate follow.class
FollowSpec.class: Spec.class GrammarUtil.class follow.class
scalac -cp ${SCALA_FLAGS} FollowSpec.scala
# How to avoid above line: scalac -cp ${SCALA_FLAGS} FollowSpec.scala
I hope, that the answer could look like that (put it to some clean directory and just run make, then look at those files with text editor/wiever):
SPECS=FirstSpec FollowSpec
EXAMPLES_PATH=../..
ROOT_PATH=../${EXAMPLES_PATH}
SCALAV=2.12
APSLIB=${ROOT_PATH}/lib/aps-library-${SCALAV}.jar
SCALA_FLAGS=.:${APSLIB}
APS2SCALA=${ROOT_PATH}/bin/aps2scala
.PHONY: all
all: $(addsuffix .run, $(SPECS))
.PHONY: clean
clean:
rm -f *.class grammar.scala first.scala follow.scala
# for anything.scala it compiles it to create class file
# Lets use ALL prequisities ($^) and create target by name (--output $#)
%.class: %.scala
echo "scalac -cp ${SCALA_FLAGS} $^ --output $#" > $#
# for anything.scala that does not already exist, it generates it
%.scala:
echo "${APS2SCALA} -DCOT -p ${EXAMPLES_PATH}:${ROOT_PATH}/base $*" >$#
# for anything.run it needs to be compiled first before running
%.run: %.class
echo "scala -cp ${SCALA_FLAGS} $(basename $<)" >$#
# for GrammarUtil.class it needs to generate and compile grammar.class and then compile itself
# for FirstSpec.class it needs to compile Spec.class, GrammarUtil.class and compile and then generate first.class
# for FollowSpec.class it needs to compile Spec.class, GrammarUtil.class and compile and then generate follow.class
GrammarUtil.class: grammar.class
FirstSpec.class: Spec.class GrammarUtil.class first.class
FollowSpec.class: Spec.class GrammarUtil.class follow.class
I have no scala, so I could not try it, but I let it put commands to create each file to that file for inspection - so it may be enought just to delete those echo " and " >$# to get the desired result.
Also I hope, that the scalacp command have some way to place output to explicitly named file - I used hypotetical --output filename switch for that. Or that is somehow get it from its first file parametr right (so the --outpu filename is not needed)
There are used 3 "tricks"
to use all from dependencies there is $^ variable
to get name of target file is variable $#
there can be more dependencies lines (like those 3 at bottom) and they use the same rule for the target, if it meets the same pattern

String manipulation in makefiles

I've replaced my many .sh files from my previous question with a makefile, of sorts. I don't know much about makefiles, admittedly, but this seems to work:
MainPackage=jnitest
Main=SimpleJNITest
cFileName=rtm_simple
targetDir=classes
libDir=lib
srcDir=src
jdkDir="/home/user/local/java/jdk1.7.0_65"
java="$(jdkDir)/bin/java"
javah="$(jdkDir)/bin/javah"
javac="$(jdkDir)/bin/javac"
JAVAC_FLAGS=-d "$(targetDir)" -sourcepath "$(srcDir)" -cp "$(targetDir):$(libDir)/*"
JAVAH_FLAGS=-d "$(ccodeDir)" -cp "$(targetDir)" -jni
JAVA_FLAGS=-Djava.library.path="$(LD_LIBRARY_PATH):$(libDir)" -cp "$(targetDir):$(libDir)/*"
ccodeDir=$(srcDir)/ccode
CC=gcc
CC_FLAGS=-g -shared -fpic -I "$(jdkDir)/include" -I "$(jdkDir)/include/linux"
cFile="$(ccodeDir)/$(cFileName).c"
soFile="$(libDir)/lib$(cFileName).so"
dirs:
mkdir -p "$(targetDir)"
mkdir -p "$(libDir)"
java: dirs
$(javac) $(JAVAC_FLAGS) "$(srcDir)/$(MainPackage)/$(Main).java"
header:
$(javah) $(JAVAH_FLAGS) "$(MainPackage).$(Main)"
c:
$(CC) $(CC_FLAGS) $(cFile) -o $(soFile)
all:
$(MAKE) java
$(MAKE) header
$(MAKE) c
run:
$(java) $(JAVA_FLAGS) "$(MainPackage).$(Main)"
clean:
rm -rf classes
rm -rf lib
rm -f $(ccodeDir)/$(MainPackage)_$(Main).h
My problem, now, lies with MainPackage=jnitest.
So long as MainPackage is a single word, everything is fine.
However, when it is not, I'll be needing it once in slash notation for
$(javac) $(JAVAC_FLAGS) "$(srcDir)/$(MainPackage)/$(Main).java"
and once in dot notation for
$(java) $(JAVA_FLAGS) "$(MainPackage).$(Main)"
In bash, you could do something like
MainPackage_slashed=$(echo "$MainPackage" | tr '.' '/')
How do I insert one such transformation into a makefile?
You're looking for the subst function, see the GNU make manual.
Example:
foo=x.y.z
bar=$(subst .,/,$(foo))
$(info $(bar))
prints x/y/z.
You will need to use shell function in your Makefile like this:
MainPackage_slashed := $(shell echo "$(MainPackage)" | tr '.' '/')

Is this a robust solution to creating output directories in a Makefile?

I've seen a few approaches to making output directories in Make.
These include making all directories ahead of time outside of any rule, and
making an object's destination directory as part of the object's rule.
Both of these approaches involve making directories that likely already exist.
Am I missing any gotchas or drawbacks that explain why I haven't seen the below approach?
.SECONDEXPANSION:
$(OBJDIR)%.o: %.c | $$(#D)/
# Compile command
.PRECIOUS: %/
%/:
# mkdir Command
make is very good at dealing with files. make is not very good at dealing with directories.
So treating directories as implementation detail internal to the target rule makes sense, because then make never has to consider the directory at all:
MKDIR_P = mkdir -p
$(objdir)%.o: %.c
#$(MKDIR_P) $(#D)
$(COMPILE.c) -o $# -c $<
Note that the processing and IO required for the mkdir -p can be neglected next to the processing and IO required for the compilation.
The problem with directories is that (contrary to any other target) you don't care for their timestamp, you only need them to exist. Many Makefiles get directories somehow wrong, and creating them over and over again is what you observe, so make will never detect "Nothing to be done for ...".
In fact, the only thing you need for correct handling of directories with GNU make is an "order only dependency", like shown in your example. The trailing slash normally isn't needed (you seem to use it in order to have a pattern rule, I'm not sure whether this works), and you don't need .PRECIOUS either. Your trick with .SECONDEXPANSION looks quite neat, I guess this will work, given the pattern rule indeed works that way (didn't try).
For an alternative, most Makefiles that handle directories correctly take a simpler approach by concatenating all needed output directories for a rule in a single variable and use this variable as a target for another rule, e.g. like in this simplified example:
MODULES:=src/main
OBJDIR?=obj
OBJS:=$(addprefix $(OBJDIR)/,$(addsuffix .c,$(MODULES)))
DIRS:=$(sort $(addprefix $(OBJDIR)/,$(dir $(OBJS))))
TARGET:= myprogram
all: $(TARGET)
myprogram: $(OBJS)
$(CC) -o$# $^
$(DIRS):
mkdir -p $(DIRS)
$(OBJDIR)/%.o: %.c Makefile | $(DIRS)
$(CC) -c -o$# $<
clean:
rm -fr $(OBJDIR)
.PHONY: all clean

GNU make ignores slash on cmd.exe

I had a problem with GNU make on cmd.exe. Somehow it ignores '/' output by dir and says there is no rule.
$(foreach f,$(OBJS),$(eval $f : | $(dir $f)))
%/:
mkdir -p $#
So I made this dirty hack.
$(foreach f,$(OBJS),$(eval $f : | $(dir $f)D))
%/D:
mkdir -p $#
Any better solution? Please do not tell me to throw the broken shell away. I don't use the shell, but others use it.
As far as I know target pattern matching doesn't have anything to do with the shell make will eventually use to run recipe targets. It is done entirely internally to make.
That said that exact makefile snippet (using OBJS := a/a.o b/b.o c/c.o) works for me with make 3.81, 3.82, 4.0 and 4.1 on CentOS 5. (I don't have make on Windows to test with.) So maybe there is something OS-specific involved here.
You could try removing the / and just using % as the target?
That all said a better solution might be to assign the directory components to a variable and use that variable as the target.
DIRS :=
$(foreach f,$(OBJS),$(eval $f : | $(dir $f)) $(eval DIRS += $(dir $f)))
$(DIRS):
mkdir -p $#

Makefile, declare variable in executable

This is a simple question for a starter like me, but what can I do to do like the following
all: run
run:
DIR=bin/
$(CC) $(LIBRARY) $(INCLUDE) run.o -o $(DIR)$#
Thanks.
Why not go like this?
DIR=bin/
all: $(DIR)/run
$(DIR)/run:
$(CC) $(LIBRARY) $(INCLUDE) run.o -o $#
As written, you have an assignment to the shell variable DIR on one command line. On the next line, you have the expansion of a make variable DIR. This doesn't work because the two lines are executed by different shells, and in any case, make expands $(DIR) before running the shell and DIR is not a make variable.
You could make it work like this:
all: run
run:
DIR=bin/; \
$(CC) $(LIBRARY) $(INCLUDE) run.o -o $${DIR}$#
The backslash means the two lines are treated as one (so much so that the semicolon is needed). The $${DIR} notation is expanded by make to ${DIR} (more precisely, $$ expands to $ and make ignores the {DIR}), and then the shell expands ${DIR} from the value set previously. You could, of course, omit the braces.
However, the answer by BeSerK is probably what you're looking for.

Resources