2016-12-15 52 views
2

我想寫一個Makefile文件,其中多個源文件(在我的情況下是markdown)創建多個目標文件(pdf)。但是,生成的目標文件在文件名中包含額外的字符,無法預測(它恰好是在源文件中編碼的版本號),但理想情況下,Makefile不必讀取源本身。目標名稱未知的Makefile文件

因此,舉例來說:

file1.md => file1-v1.pdf 
file2.md => file2-v2.pdf 
... 

我可以計算給定一個目標名稱(排除連字符後的任何東西,加入.MD)源名稱,但不能計算目標名稱給出的來源。

是否可以編寫一個Makefile來構建源代碼已經更新的目標?

+0

你願意有一些額外的標記文件嗎? – Beta

+0

我會對有或沒有解決方案感興趣。很明顯,沒有更好的,但如果這是不可能的...... – wannymahoots

回答

2

這將是醜陋的,但它會工作。

因爲它往往是與製作,我們的問題分爲以下兩個問題:
1.構建一個目標列表
2.建立他們

假設我們有五個MD文件,這映射爲PDF文件(他們的名字我們不事先知道):

file1.md => file1-v1.pdf 
file2.md => file2-v1.pdf 
file3.md => file3-v1.pdf 
file4.md => file4-v1.pdf 
file5.md => file5-v1.pdf 

我們不能用真實的輸出文件名作爲目標,因爲我們不知道他們事前,但我們看到五個輸入文件,並知道我們必須爲每個構建一個輸出文件。 現在,假目標名稱將做

file1-dummy.pdf: file1.md 
    zap file1.md 

當make執行這個規則,它產生的文件file1-v1.pdf。它不生成名爲file1-dummy.pdf的文件的事實令人不安,但不是一個嚴重的問題。我們可以把它變成一個模式規則:

%-dummy.pdf: %.md 
    zap $< 

那麼我們要做的就是把現有的輸入文件(file1.mdfile2.md ...)的名單到的假目標(file1-dummy.pdffile2-dummy.pdf名單, ...),並建立它們。到現在爲止還挺好。

但是假設一些輸出文件已經存在。如果file2-v2.pdf已經存在 - 並且比file2.md更新 - 那麼我們寧願Make不重建它(通過嘗試構建file2-dummy.pdf)。在這種情況下,我們更希望file2-v2.pdf是在目標列表中,用規則的工作是這樣的:

file2-v2.pdf: file2.md 
    zap $< 

不容易變成一個模式規則,因爲製作不處理通配符很好,並且不能用一個單詞來處理多個通配符,而不是沒有很多笨拙。但是有一種方法可以編寫一個覆蓋兩種情況的規則。首先要注意的是,我們可以用這個雜牌連字符之前獲得可變的部分:

$(basename $(subst -,.,$(VAR))) 

有了這些,並與secondary expansion,我們可以寫一個模式規則,將與這兩種情況下工作,構建目標名單將利用它:

# There are other ways to construct these two lists, but this will do. 
MD := $(wildcard *.md) 
PDF := $(wildcard *.pdf) 

PDFROOTS := $(basename $(subst -,.,$(basename $(PDF)))) 
MDROOTS := $(filter-out $(PDFROOTS), $(basename $(MD))) 

TARGETS:= $(addsuffix -foo.pdf, $(MDROOTS)) $(PDF) 

.SECONDEXPANSION: 
%.pdf: $$(basename $$(subst -,., $$*)).md 
    # perform actions on $< 
1

Make的算法總是從最終的輸出產品開始,然後回溯到源文件,以查看需要更新的內容。

因此,您必須能夠枚舉最終的輸出產品作爲目標名稱,並將其返回到生成該輸出的輸入,以使make正常工作。

這也是爲什麼make不是構建Java的好工具,例如,因爲輸出文件名不容易映射到輸入文件名。

因此,您必須至少有一個目標/先決條件對可導出(用於隱式規則)或可狀態(用於顯式規則) - 即在編寫生成文件時已知的目標/先決條件對。如果你不這樣做,那麼一個標記文件是你唯一的選擇。請注意,除了已知的先決條件外,您還可以添加額外生成的非衍生先決條件(例如,在編譯器中,您可以添加頭文件作爲與源文件名無關的先決條件)。

1

@貝塔的回答是豐富和有益的,但我需要的時候目標文件名沒有任何相似之處,以輸入文件名,例如奏效的解決方案(使用GNU讓4.1),如果它是從其內容生成的。我想出了以下內容,它將每個文件與*.in匹配,並通過讀取源文件的內容創建一個文件,附加一個.txt,並將其用作要創建的文件名。 (例如,如果test.in存在幷包含foo,生成文件將創建一個foo.txt文件。)

SRCS := $(wildcard *.in) 
.PHONY: all 

all: all_s 

define TXT_template = 
$(2).txt: $(1) 
    touch [email protected] 
ALL += $(2).txt 
endef 

$(foreach src,$(SRCS),$(eval $(call TXT_template, $(src), $(shell cat $(src))))) 

.SECONDARY_EXPANSION: 
all_s: $(ALL) 

的解釋:

define塊定義從.in文件使文本文件所需的食譜。這是一個帶有兩個參數的函數; $(1).in.文件,$(2)是其內容或輸出文件名的基礎。將touch替換爲輸出的任何內容。我們必須使用[email protected],因爲eval將一次性擴展所有內容,但我們希望[email protected]在此擴展之後離開。由於我們必須收集所有生成的目標,因此我們知道所有的生產線,ALL生產線將目標累積爲一個變量。 foreach行會遍歷每個源文件,用源文件名和文件內容調用函數(即我們想要成爲目標的名稱,這裏通常使用任何腳本生成所需的文件名),以及然後評估結果塊,動態地將配方添加到make。感謝Beta的解釋.SECONDARY_EXPANSION;我需要它的原因並不完全清楚,但它起作用(將all: $(ALL)置於頂端不起作用)。頂部的all:取決於底部的all_s:的二次擴展,並且不知何故,這種魔法使其發揮作用。評論歡迎。