Node.JS
Node.JS

Reputation: 1568

Weird Makefile behavior where file needs to be generated first and the compiled

I am getting weird behavior in Makefile. Basically for grammar.aps, it needs to run grammar.generate to create the file first and then grammar.class to compile the generated file. If I run the dependencies individually all works. But if I run grammar.aps it doesn't work.

// Running everything
➜  tests git:(first-follow-tests) ✗ make
scalac -cp .:../../../lib/aps-library-2.12.jar Spec.scala
make: *** No rule to make target 'grammar.class', needed by 'grammar.aps'.  Stop.

// Now running first depdency of `grammar.aps` which is `grammar.generate`
➜  tests git:(first-follow-tests) ✗ make grammar.generate
../../../bin/aps2scala -DCOT -p ../..:../../../base grammar

// Now running second depdency of `grammar.aps` which is `grammar.generate`
➜  tests git:(first-follow-tests) ✗ make grammar.class
scalac -cp .:../../../lib/aps-library-2.12.jar grammar.scala

// Okay but why grammar.aps didn't work? ... Makefile continues further now
➜  tests git:(first-follow-tests) ✗ make                 
scalac -cp .:../../../lib/aps-library-2.12.jar grammar.scala
../../../bin/aps2scala -DCOT -p ../..:../../../base grammar
echo "Building aps"
Building aps
scalac -cp .:../../../lib/aps-library-2.12.jar GrammarUtil.scala
echo "Building GrammarUtil.scala"
Building GrammarUtil.scala

// Same problem with `first.aps`
make: *** No rule to make target 'first.aps', needed by 'FirstSpec.compile'.  Stop.

Makefile:

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

.PHONY:
all: $(addsuffix Spec.compile, $(SPECS))

%.class: %.scala
    scalac -cp ${SCALA_FLAGS} $<

%.generate:
    ${APS2SCALA} -DCOT -p ${EXAMPLES_PATH}:${ROOT_PATH}/base $*

%.aps: %.generate %.class
    echo "Building aps"

%.run: %.class
    @scala -cp ${SCALA_FLAGS} $<

FirstSpec.compile: Spec.class GrammarUtil.compile first.aps FirstSpec.class 
    echo "Building FirstSpec.scala"

FollowSpec.compile: Spec.class GrammarUtil.compile follow.aps FollowSpec.class
    echo "Building FollowSpec.scala"

GrammarUtil.compile: grammar.aps grammar.class GrammarUtil.class
    echo "Building GrammarUtil.scala"

.PHONY:
clean:
    rm -f *.class grammar.scala first.scala follow.scala

Upvotes: 0

Views: 47

Answers (1)

John Bollinger
John Bollinger

Reputation: 180256

I am getting weird behavior in Makefile. Basically for grammar.aps, it needs to run grammar.generate to create the file first and then grammar.class to compile the generated file. If I run the dependencies individually all works. But if I run grammar.aps it doesn't work.

You are trying to treat your makefile as if it were a script. It isn't. At the most basic level, it is a declarative description of your project that tells make how it can construct one or more targets given specific lists of prerequisites for each. Each prerequisite list is only incidentally ordered. That one prerequisite appears lexically ahead of another has no significance. It is up to make to choose and execute a build plan based on the information in the makefile and the files available.

In particular, make performs its analysis of which intermediate targets to build based on the prerequisites available at the start of the run, in light of the declared build rules. Furthermore, prerequisite build order is constrained only by prerequisites' own prequisites, and it is not necessarily predictable.

However, make does give you pretty informative error messages. For example, this ...

make: *** No rule to make target 'grammar.class', needed by 'grammar.aps'.  Stop.

... is pretty clear: target grammar.aps needs to be built, and it has grammar.class as a prerequisite, but there is no rule for building grammar.class. That is, there is no rule having grammar.class as its target. That building grammar.generate would in fact create grammar.class is irrelevant because you haven't expressed that to make.

You could fix this particular issue by converting your rule for %.generate into a rule for %.class:

%.class:
    ${APS2SCALA} -DCOT -p ${EXAMPLES_PATH}:${ROOT_PATH}/base $*

%.aps: %.class
    echo "Building aps"

More generally, you should prefer to write rules with real targets over rules with phony ones. Phony targets are not necessarily bad, but they are not a viable substitute for real ones.

Upvotes: 1

Related Questions