2

I am a Makefile novice and I want to use it to render Rmarkdown files when they have altered timestamps, to produce the corresponding .pdf files. The file data_prep_1.Rmd is rendered to .pdf and additionally produces the output file all_sample_data.csv. When data_prep_1.Rmd is changed (or its timestamp is altered), it should be re-rendered, as should ../bioinformatics/Reads_by_sample.Rmd and distance_matrices.Rmd because they depend on the .csv file. If the timestamp of ../bioinformatics/Reads_by_sample.Rmd is changed relative to its .pdf, only this .Rmd file should be re-rendered.

Putting the all : and the .pdf and .csv file in the first line will lead both blocks to run each time make is called.
I have:

../../data/all_sample_data.csv : data_prep_1.Rmd
    Rscript -e "rmarkdown::render('data_prep_1.Rmd')"
    Rscript -e "rmarkdown::render('../bioinformatics/Reads_by_sample.Rmd')"
    Rscript -e "rmarkdown::render('distance_matrices.Rmd')"

../bioinformatics/Reads_by_sample.pdf :  ../bioinformatics/Reads_by_sample.Rmd
    Rscript -e "rmarkdown::render('../bioinformatics/Reads_by_sample.Rmd')"

If I do touch data_prep_1.Rmd, then make, the first block runs as desired.
But if I do touch ../bioinformatics/Reads_by_sample.Rmd, then make

I get make: '../../data/all_sample_data.csv is up to date'.

I know this is clumsy use of Makefile, but how can I get the behavior I want in a single Makefile?

bobbogo
  • 14,989
  • 3
  • 48
  • 57
Peter Pearman
  • 129
  • 1
  • 10
  • Possible duplicate of [What is the purpose of .PHONY in a makefile?](https://stackoverflow.com/questions/2145590/what-is-the-purpose-of-phony-in-a-makefile) – user657267 Nov 09 '17 at 12:08
  • 1
    You need to add a `PHONY` target such as `all` that has `../../data/all_sample_data.csv` and `../bioinformatics/Reads_by_sample.pdf` as prerequisites, and is the first target in the makefile. – user657267 Nov 09 '17 at 12:09
  • But then won't all the commands in the first block be run, even when only Reads_by_sample.Rmd has been changed? I'd like to avoid rendering when the prerequisite hasn't changed. – Peter Pearman Nov 09 '17 at 12:53
  • Unless you modify `data_prep_1.Rmd` the recipes for the first rule won't be run. – user657267 Nov 09 '17 at 14:16

1 Answers1

1

The comments above are confusing but hide the answer.

When make runs, it doesn't try to build every single target in the makefile. Instead, it only tries to build the first target in the makefile. In your makefile the first target is ../../data/all_sample_data.csv and so that's the only target make will try to build.

You can get it to build other targets by adding them to the command line; for example make ../bioinformatics/Reads_by_sample.pdf.

If you always want a certain set of targets what you do is create a new target as the first target in the makefile, that lists all the other targets you want to create as prerequisites. By convention, but only convention, that target is typically called all. So it would look like this:

all: ../../data/all_sample_data.csv ../bioinformatics/Reads_by_sample.pdf
.PHONY: all

../../data/all_sample_data.csv : data_prep_1.Rmd
        Rscript -e "rmarkdown::render('data_prep_1.Rmd')"
        Rscript -e "rmarkdown::render('../bioinformatics/Reads_by_sample.Rmd')"
        Rscript -e "rmarkdown::render('distance_matrices.Rmd')"

../bioinformatics/Reads_by_sample.pdf :  ../bioinformatics/Reads_by_sample.Rmd
        Rscript -e "rmarkdown::render('../bioinformatics/Reads_by_sample.Rmd')"

The .PHONY is not required but is useful, it tells make that all is not a real file, but rather just a dummy target in the makefile.

You should also consider creating some make variables and using automatic variables to simplify your makefile, for example:

BIODIR := ../bioinformatics
DATADIR := ../../data

all: $(DATADIR)/all_sample_data.csv $(BIODIR)/Reads_by_sample.pdf
.PHONY: all

$(DATADIR)/all_sample_data.csv : data_prep_1.Rmd
        Rscript -e "rmarkdown::render('$<')"
        Rscript -e "rmarkdown::render('$(BIODIR)/Reads_by_sample.Rmd')"
        Rscript -e "rmarkdown::render('distance_matrices.Rmd')"

$(BIODIR)/Reads_by_sample.pdf :  $(BIODIR)/Reads_by_sample.Rmd
        Rscript -e "rmarkdown::render('$<')"

You can go further and use pattern rules etc. if you have lots of these types of files.

Also, it appears that your first target should depend not just on data_prep_1.Rmd but also on distance_matrices.Rmd and Reads_by_sample.Rmd; don't you want to rebuild if any of those changes?

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • 1
    In your last comment, you mean All should have the two .Rmd files as dependencies, right? So an additional target and dependency would be: ./geo_distances_populations.csv : distance_matrices.Rmd Rscript -e "rmarkdown::render('distance_matrices.Rmd')" and the .csv file would go into all as well? – Peter Pearman Nov 10 '17 at 15:39
  • You can list all the prerequisites in the same rule line if you want. I'm not really sure what aspect of that recipe causes the output CSV file to be created. But you can say: `.../all_sample_data.csv : data_prep_1.Rmd distance_matrices.Rmd .../Reads_by_sample.Rmd` – MadScientist Nov 10 '17 at 18:40