Tek bir kaynak dosyadan birkaç hedef oluşturan GNU Makefile kuralı


106

Aşağıdakileri yapmaya çalışıyorum. foo-binTek bir girdi dosyası alan ve iki çıktı dosyası oluşturan bir program var . Bunun için aptalca bir Makefile kuralı şöyle olacaktır:

file-a.out file-b.out: input.in
    foo-bin input.in file-a.out file-b.out

Ancak bu makehiçbir şekilde her iki hedefin aynı anda oluşturulacağını söylemez . makeSeri olarak koşarken bu iyidir , ancak denerse make -j16veya eşit derecede çılgınca bir şey olursa muhtemelen sorun çıkarır.

Soru, böyle bir durum için uygun bir Makefile kuralı yazmanın bir yolu olup olmadığıdır? Açıkça, bir DAG oluşturacaktır, ancak bir şekilde GNU yapım kılavuzu bu durumun nasıl ele alınacağını belirtmez.

Aynı kodu iki kez çalıştırmak ve yalnızca bir sonuç üretmek söz konusu değildir, çünkü hesaplama zaman alır (düşünün: saat). Yalnızca bir dosyanın çıktısını almak da oldukça zor olacaktır, çünkü genellikle bir veri dosyasının sadece bir kısmını nasıl kullanacağını bilmeyen GNUPLOT'a girdi olarak kullanılır.


1
Automake bununla ilgili belgelere sahiptir: gnu.org/software/automake/manual/html_node/…
Frank

Yanıtlar:


12

Bunu şu şekilde çözerdim:

file-a.out: input.in
    foo-bin input.in file-a.out file-b.out   

file-b.out: file-a.out
    #do nothing
    noop

Bu durumda paralel oluşturma, a ve b'yi oluşturarak 'serileştirir', ancak b'yi oluşturmak hiçbir şey yapmadığından zaman almaz.


16
Aslında bununla ilgili bir sorun var. Eğer file-b.outbelirtilen ve daha sonra gizemli bir şekilde silinmiş olarak oluşturuldu, makeçünkü yeniden oluşturmanız mümkün olmayacaktır file-a.outhala mevcut olacak. Düşüncesi olan var mı?
makeaurus

Gizemli bir şekilde silerseniz file-a.out, yukarıdaki çözüm iyi çalışır. Bu, bir hacklemeyi önerir: a veya b'den yalnızca biri mevcut olduğunda, bağımlılıklar, eksik dosyanın çıktısı olarak görünecek şekilde sıralanmalıdır input.in. Birkaç $(widcard...)s, $(filter...)s, $(filter-out...)s vb. Hile yapmalı. Ugh.
bobbogo

3
PS foo-binaçıkça ilk olarak dosyalardan birini oluşturacaktır (mikrosaniye çözünürlük kullanarak), bu nedenle her iki dosya da mevcut olduğunda doğru bağımlılık sıralamasına sahip olduğunuzdan emin olmalısınız.
bobbogo

2
@bobbogo ya bu, ya da çağrıdan touch file-a.outsonra ikinci komut olarak ekleyin foo-bin.
Connor Harris

İşte bunun için olası bir düzeltme: touch file-b.outilk kurala ikinci komut olarak ekleyin ve ikinci kuraldaki komutları çoğaltın. Dosyalardan herhangi biri eksikse veya daha eski ise input.in, komut yalnızca bir kez çalıştırılacak ve her iki dosyayı da 'dan file-b.outdaha yeni ile yeniden oluşturacaktır file-a.out.
chqrlie

128

İşin püf noktası, birden çok hedefi olan bir model kuralı kullanmaktır. Bu durumda make her iki hedefin de tek bir komut çağrısı ile oluşturulduğunu varsayacaktır.

all: dosya-a.out dosyası-b.out
dosya-a% çıkış dosyası-b% çıkışı: input.in
    foo-bin input.in dosya-a $ * çıkış dosyası-b $ * çıkış

Kalıp kuralları ile normal kurallar arasındaki bu yorum farkı tam olarak mantıklı değildir, ancak bu gibi durumlar için yararlıdır ve kılavuzda belgelenmiştir.

Bu numara, adlarının %eşleşmesi için ortak bir alt dizeye sahip olduğu sürece herhangi bir sayıda çıktı dosyası için kullanılabilir . (Bu durumda ortak alt dize "." Dir)


3
Bu aslında doğru değil. Kuralınız, bu kalıplarla eşleşen dosyaların, belirtilen komut kullanılarak input.in'den yapılacağını belirtir, ancak hiçbir yerde aynı anda yapıldıklarını söylemez. Aslında paralel olarak makeçalıştırırsanız, aynı komutu aynı anda iki kez çalıştıracaktır.
makeaurus

47
Lütfen işe yaramadığını söylemeden önce deneyin ;-) GNU yapma kılavuzu şunu söyler: "Kalıp kurallarının birden fazla hedefi olabilir. Normal kuralların aksine, bu aynı önkoşullara ve komutlara sahip birçok farklı kural kadar davranmaz. bir model kuralının birden fazla hedefi varsa, kuralın komutlarının tüm hedefleri yapmaktan sorumlu olduğunu bilin. Komutlar, tüm hedefleri yapmak için yalnızca bir kez yürütülür. " gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog

4
Peki ya tamamen farklı girdilerim varsa ?, foo.in ve bar.src deyin? Kalıp kuralları boş kalıp eşleşmesini destekliyor mu?
grwlf

2
@dcow: Çıktı dosyalarının yalnızca bir alt dizeyi paylaşması gerekir ("." gibi tek bir karakter bile yeterlidir) mutlaka bir önek değildir. Ortak bir alt dize yoksa richard ve deemer tarafından açıklandığı gibi bir ara hedef kullanabilirsiniz. @grwlf: Hey, bunun anlamı "foo.in" ve "bar.src" yapılabilir: foo%in bar%src: input.in:-)
slowdog

1
İlgili çıktı dosyaları değişkenlerde tutulursa, tarif şu şekilde yazılabilir: $(subst .,%,$(varA) $(varB)): input.in(GNU make 4.1 ile test edilmiştir).
stefanct

55

Make'in bunu yapmanın sezgisel bir yolu yoktur, ancak iki makul geçici çözüm vardır.

İlk olarak, ilgili hedeflerin ortak bir kökü varsa, bir önek kuralı kullanabilirsiniz (GNU make ile). Yani, aşağıdaki kuralı düzeltmek istiyorsanız:

object.out1 object.out2: object.input
    foo-bin object.input object.out1 object.out2

Bunu şu şekilde yazabilirsin:

%.out1 %.out2: %.input
    foo-bin $*.input $*.out1 $*.out2

(Desenin eşleşen kısmını temsil eden desen kuralı değişkeni $ * kullanılarak)

GNU Make dışı uygulamalara taşınabilir olmak istiyorsanız veya dosyalarınız bir kalıp kuralıyla eşleşecek şekilde adlandırılamıyorsa, başka bir yol vardır:

file-a.out file-b.out: input.in.intermediate ;

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

Bu, input.in.intermediate'in make çalıştırılmadan önce var olmayacağını söyler, böylece yokluğu (veya zaman damgası) foo-bin'in sahte bir şekilde çalıştırılmasına neden olmaz. Ve ister dosya-a.out, ister dosya-b.out isterse her ikisi de güncel değil (input.in'e göre), foo-bin yalnızca bir kez çalıştırılacaktır. .INTERMEDIATE yerine .SECONDARY kullanabilirsiniz; bu, varsayımsal bir dosya adını input.in.intermediate silmemenizi sağlayacak. Bu yöntem, paralel yapım derlemeleri için de güvenlidir.

İlk satırdaki noktalı virgül önemlidir. Bu kural için boş bir tarif oluşturur, böylece Make file-a.out ve file-b.out'u gerçekten güncelleyeceğimizi bilir (teşekkürler @siulkiulki ve buna işaret eden diğerleri)


7
Güzel, görünüyor .INTERMEDIATE yaklaşımı iyi çalışıyor. Ayrıca sorunu açıklayan Automake makalesine de bakın (ancak bunu taşınabilir bir şekilde çözmeye çalışıyorlar).
grwlf

3
Buna gördüğüm en iyi cevap bu. Diğer özel hedefler de ilgi çekici olabilir: gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide

2
@deemer Bu benim için OSX'te çalışmıyor. (GNU Make 3.81)
Jorge Bucaran

2
Bunu deniyordum ve paralel derlemelerle ilgili ince problemler fark ettim --- bağımlılıklar her zaman ara hedef boyunca düzgün bir şekilde yayılmıyor (her ne kadar -j1derlemelerde her şey yolunda olsa da ). Ayrıca makefile kesintiye uğrarsa ve input.in.intermediatedosyayı etrafında bırakırsa gerçekten kafası karışır.
David Verilen

3
Bu cevapta bir hata var. Mükemmel çalışan paralel yapılara sahip olabilirsiniz. Sadece değiştirmek zorunda file-a.out file-b.out: input.in.intermediateiçin file-a.out file-b.out: input.in.intermediate ;bkz stackoverflow.com/questions/37873522/... fazla ayrıntı için.
siulkilulki

10

Bu, desen kurallarına dayanmayan @ deemer'in ikinci cevabına dayanmaktadır ve geçici çözümün iç içe kullanımlarında yaşadığım bir sorunu düzeltir.

file-a.out file-b.out: input.in.intermediate
    @# Empty recipe to propagate "newness" from the intermediate to final targets

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

Bunu @ deemer'in cevabına bir yorum olarak eklerdim, ancak yapamam çünkü bu hesabı yeni oluşturdum ve herhangi bir itibarım yok.

Açıklama: Boş tarifi yap işaretine doğru kayıtları tutmasını sağlamak için gereklidir file-a.outve file-b.outbeen yeniden inşa sahip olarak. Bağımlı olan başka bir ara hedefiniz varsa file-a.out, Make dış ara hedefi oluşturmamayı seçecek ve şunu iddia edecek:

No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.

10

GNU Make 4.3'ten (19 Ocak 2020) sonra "gruplandırılmış açık hedefler" kullanabilirsiniz. Değiştir :ile &:.

file-a.out file-b.out &: input.in
    foo-bin input.in file-a.out file-b.out

GNU Make'in NEWS dosyası şunu söylüyor:

Yeni özellik: Gruplanmış açık hedefler

Kalıp kuralları, her zaman tek bir tarif çağrısı ile birden çok hedef oluşturma yeteneğine sahip olmuştur. Artık, açık bir kuralın tek bir çağrı ile birden çok hedef oluşturduğunu bildirmek mümkün. Bunu kullanmak için, kuraldaki ":" simgesini "&:" ile değiştirin. Bu özelliği tespit etmek için .FEATURES özel değişkeninde 'gruplanmış hedef' araması yapın. Kaz Kylheku <kaz@kylheku.com> tarafından sağlanan uygulama


2

Ben böyle yaparım. Öncelikle her zaman ön talepleri tariflerden ayırıyorum. Sonra bu durumda tarifi yapacak yeni bir hedef.

all: file-a.out file-b.out #first rule

file-a.out file-b.out: input.in

file-a.out file-b.out: dummy-a-and-b.out

.SECONDARY:dummy-a-and-b.out
dummy-a-and-b.out:
    echo creating: file-a.out file-b.out
    touch file-a.out file-b.out

İlk seferinde:
1. Dosya-a.out'u oluşturmaya çalışıyoruz, ancak ilk önce kukla-a-ve-b.out'un yapması gerekiyor, bu yüzden kukla-a-ve-b.out tarifini çalıştırmasını sağlayın.
2. Dosya-b.out oluşturmaya çalışıyoruz, dummy-a-and-b.out güncel.

İkinci ve sonraki zaman:
1. file-a.out'u oluşturmaya çalışıyoruz: ön koşullara göz atalım, normal ön koşullar güncel, ikincil ön koşullar eksik olduğundan yok sayılır.
2. file-b.out oluşturmaya çalışıyoruz: ön koşullara göz atalım, normal ön koşullar güncel, ikincil önkoşullar eksik, bu yüzden yok sayılıyor.


1
Bu bozuk. Eğer make'i silerseniz file-b.outonu yeniden yaratmanın bir yolu yoktur.
bobbogo

tümü eklendi: ilk kural olarak dosya-a.out dosya-b.out. Sanki file-b.out kaldırılmış ve make hedef olmadan komut satırında çalıştırılmış gibi, onu yeniden oluşturmazdı.
ctrl-alt-delor

@bobbogo Düzeltildi: yukarıdaki yoruma bakın.
ctrl-alt-delor

0

@ Deemer'in cevabının bir uzantısı olarak, onu bir fonksiyona genelleştirdim.

sp :=
sp +=
inter = .inter.$(subst $(sp),_,$(subst /,_,$1))

ATOMIC=\
    $(eval s1=$(strip $1)) \
    $(eval target=$(call inter,$(s1))) \
    $(eval $(s1): $(target) ;) \
    $(eval .INTERMEDIATE: $(target) ) \
    $(target)

$(call ATOMIC, file-a.out file-b.out): input.in
    foo-bin input.in file-a.out file-b.out

Yıkmak:

$(eval s1=$(strip $1))

İlk bağımsız değişkenden baştaki / sondaki boşlukları çıkarın

$(eval target=$(call inter,$(s1)))

Ara hedef olarak kullanmak için benzersiz bir değere değişken bir hedef oluşturun. Bu durum için değer .inter.file-a.out_file-b.out olacaktır.

$(eval $(s1): $(target) ;)

Bir bağımlılık olarak benzersiz hedefi olan çıktılar için boş bir tarif oluşturun.

$(eval .INTERMEDIATE: $(target) ) 

Benzersiz hedefi bir ara ürün olarak bildirin.

$(target)

Benzersiz hedefe referansla bitirin, böylece bu işlev doğrudan bir tarifte kullanılabilir.

Ayrıca, burada eval kullanımının eval genişlememesi nedeniyle işlevin tam genişlemesinin yalnızca benzersiz hedef olması olduğunu unutmayın.

Bu işlevin esinlendiği GNU Make'deki Atomik Kurallara atıfta bulunulmalıdır .


-1

Birden çok çıkışlı bir kuralın birden çok paralel yürütülmesini önlemek için make -jNkullanın .NOTPARALLEL: outputs. Senin durumunda :

.NOTPARALLEL: file-a.out file-b.out

file-a.out file-b.out: input.in
    foo-bin input.in file-a.out file-b.out
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.