Создание правил 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. Если вы явно не устанавливаете множество зависимостей между файлами, вы упускаете много возможностей сделать. Но, чтобы потерпеть неудачу, иногда бывает сложно сделать все правильные зависимости (особенно если вы производите несколько выходов с одного шага).