recursionmakefilemultiple-makefiles

How to make a single makefile that applies the same command to sub-directories?


For clarity, I am running this on windows with GnuWin32 make.

I have a set of directories with markdown files in at several different levels - theoretically they could be in the branch nodes, but I think currently they are only in the leaf nodes. I have a set of pandoc/LaTeX commands to run to turn the markdown files into PDFs - and obviously only want to recreate the PDFs if the markdown file has been updated, so a makefile seems appropriate.

What I would like is a single makefile in the root, which iterates over any and all sub-directories (to any depth) and applies the make rule I'll specify for running pandoc.

From what I've been able to find, recursive makefiles require you to have a makefile in each sub-directory (which seems like an administrative overhead that I would like to avoid) and/or require you to list out all the sub-directories at the start of the makefile (again, would prefer to avoid this).

Theoretical folder structure:

root
|-make
|-Folder AB
|   |-File1.md
|   \-File2.md
|-Folder C
| \-File3.md
\-Folder D
  |-Folder E
  | \-File4.md
  |-Folder F
    \-File5.md

How do I write a makefile to deal with this situation?


Solution

  • Here is a small set of Makefile rules that hopefuly would get you going

    %.pdf : %.md
            pandoc -o $@ --pdf-engine=xelatex $^
    
    PDF_FILES=FolderA/File1.pdf FolderA/File2.pdf \
       FolderC/File3.pdf FolderD/FolderE/File4.pdf FolderD/FolderF/File5.pdf
    
    all: ${PDF_FILES}
    

    Let me explain what is going on here. First we have a pattern rule that tells make how to convert a Markdown file to a PDF file. The --pdf-engine=xelatex option is here just for the purpose of illustration.

    Then we need to tell Make which files to consider. We put the names together in a single variable PDF_FILES. This value for this variable can be build via a separate scripts that scans all subdirectories for .md files.

    Note that one has to be extra careful if filenames or directory names contain spaces.

    Then we ask Make to check if any of the PDF_FILES should be updated.

    If you have other targets in your makefile, make sure that all is the first non-pattern target, or call make as make all

    Updating the Makefile

    If shell functions works for you and basic utilities such as sed and find are available, you could make your makefile dynamic with a single line.

    %.pdf : %.md
            pandoc -o $@ --pdf-engine=xelatex $^
    
    PDF_FILES:=$(shell find -name "*.md" | xargs echo | sed 's/\.md/\.pdf/g' )
    all: ${PDF_FILES}
    

    MadScientist suggested just that in the comments

    Otherwise you could implement a script using the tools available on your operating system and add an additional target update: that would compute the list of files and replace the line starting with PDF_FILES with an updated list of files.