Создание правил make для зависимостей между целевыми объектами в подкаталогах проекта

Дерево исходного кода (R) для моего программного обеспечения для исследований исследований отражает традиционный рабочий процесс исследований: "собирать данные → готовить данные → анализировать данные → собирать результаты → публиковать результаты". Я использую make для создания и поддержания рабочего процесса (большинство подкаталогов проекта содержат файлы Makefile).

Однако часто мне нужно выполнять отдельные части моего рабочего процесса через определенные цели Makefile в подкаталогах проекта (а не через верхний уровень Makefile). Это создает проблему настройки Makefile правил для поддержки зависимостей между целями из разных частей рабочего процесса, другими словами - между целями в файлах Makefile, расположенных в разных подкаталогах.

Ниже представлен setup для моего проекта диссертации:

+-- diss-floss (Project root)
|-- import (data collection)
|-- cache (R data objects (), representing different data sources, in sub-directories)
|-+ prepare (data cleaning, transformation, merging and sampling)
  |-- R modules, including 'transform.R'
|-- analysis (data analyses, including exploratory data analysis (EDA))
  |-- R modules, including 'eda.R'
|-+ results (results of the analyses, in sub-directories)
  |-+ eda (*.svg, *.pdf, ...)
  |-- ...
|-- present (auto-generated presentation for defense)

Фрагменты целей из некоторых моих файлов Makefile:

"~/diss-floss/Makefile" (почти полный):

# Major variable definitions

PROJECT="diss-floss"
HOME_DIR="~/diss-floss"
REPORT={$(PROJECT)-slides}

COLLECTION_DIR=import
PREPARATION_DIR=prepare
ANALYSIS_DIR=analysis
RESULTS_DIR=results
PRESENTATION_DIR=present

RSCRIPT=Rscript

# Targets and rules 

all: rprofile collection preparation analysis results presentation

rprofile:
    R CMD BATCH ./.Rprofile

collection:
    cd $(COLLECTION_DIR) && $(MAKE)

preparation: collection
    cd $(PREPARATION_DIR) && $(MAKE)

analysis: preparation
    cd $(ANALYSIS_DIR) && $(MAKE)

results: analysis
    cd $(RESULTS_DIR) && $(MAKE)

presentation: results
    cd $(PRESENTATION_DIR) && $(MAKE)


## Phony targets and rules (for commands that do not produce files)

#.html
.PHONY: demo clean

# run demo presentation slides
demo: presentation
    # knitr(Markdown) => HTML page
    # HTML5 presentation via RStudio/RPubs or Slidify
    # OR
    # Shiny app

# remove intermediate files
clean:
    rm -f tmp*.bz2 *.Rdata

"~/дисс-зубочистка/импорт/Makefile":

importFLOSSmole: getFLOSSmoleDataXML.R
    @$(RSCRIPT) $(R_OPTS) $<
...

"~/дисс-зубочистка/подготовки/Makefile":

transform: transform.R
    $(RSCRIPT) $(R_OPTS) $<
...

"~/дисс-нить/анализ/Makefile":

eda: eda.R
    @$(RSCRIPT) $(R_OPTS) $<

В настоящее время меня беспокоит создание следующей зависимости:

Данные, собранные путем создания цели из Makefile в import, всегда должны быть преобразованы путем создания соответствующей цели из Makefile в prepare перед анализом через, например, eda.R. Если я вручную запустил make в import, а затем, забыв о преобразовании, запустите make eda в analyze, все идет не слишком хорошо. Поэтому мой вопрос:

Как я могу использовать функции утилиты make (самым простым способом) для установления и поддержания правил зависимостей между целями из Makefile файлов в разных каталогах?

Ответ 1

Ниже приводятся мои мысли (с некоторыми идеями из ответа @MrFlick - спасибо) о добавлении моей исследовательской рабочей последовательности зависимостей данных к текущей инфраструктуре make проекта (с фрагментами кода). Я также попытался отразить желаемый рабочий процесс, указав зависимости между make целями.

импорт /Makefile:

importFLOSSmole: getFLOSSmoleDataXML.R FLOSSmole.RData
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

(similar targets for other data sources)

подготовить /Makefile:

IMPORT_DIR=../import

prepare: import \
         transform \
         cleanup \
         merge \
         sample

import: $IMPORT_DIR/importFLOSSmole.done # and/or other flag files, as needed

transform: transform.R import
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

cleanup: cleanup.R transform
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

merge: merge.R cleanup
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

sample: sample.R merge
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

Анализ /Makefile:

PREP_DIR=../prepare

analysis: prepare \
          eda \
          efa \
          cfa \
          sem

prepare: $PREP_DIR/transform.done # and/or other flag files, as needed

eda: eda.R prepare
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

efa: efa.R eda
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

cfa: cfa.R efa
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

sem: sem.R cfa
    @$(RSCRIPT) $(R_OPTS) $<
    @touch [email protected]

Содержимое Makefile файлов в каталогах results и present по-прежнему является TBD.

Буду признателен за ваши мысли и советы по поводу вышеизложенного!

Ответ 2

Проблема с вашим использованием makefile прямо сейчас заключается в том, что вы указываете только код как зависимости, а не данные. То, что происходит много волшебства. Если "анализ" знал, какие файлы он будет использовать, и может отображать их как зависимости, он может оглянуться назад, чтобы посмотреть, как они были сделаны и какие зависимости у них были. И если предыдущий файл в конвейере был обновлен, он мог бы выполнить все необходимые шаги, чтобы обновить файл. Например

import: rawdata.csv

rawdata.csv:
    scp remoteserver:/rawdata.csv .

transform: tansdata.csv

transdata.csv: gogo.pl rawdata.csv
    perl gogo.pl $< > [email protected]

plot: plot.png

plot.png: plot.R transdata.csv
    Rscript plot.R

Итак, если я сделаю make import, он загрузит новый файл csv. Тогда, если я запустил make plot, он попытается сделать plot.png, но это зависит от transdata.csv, и это зависит от rawdata.csv, и поскольку rawdata.csv был обновлен, ему нужно будет обновить transdata.csv, а затем он будет готов запустить R script. Если вы явно не устанавливаете множество зависимостей между файлами, вы упускаете много возможностей сделать. Но, чтобы потерпеть неудачу, иногда бывает сложно сделать все правильные зависимости (особенно если вы производите несколько выходов с одного шага).