Aturan GNU Makefile menghasilkan beberapa target dari satu file sumber

106

Saya mencoba melakukan hal berikut. Ada sebuah program, sebut saja foo-bin, yang mengambil satu file masukan dan menghasilkan dua file keluaran. Aturan Makefile bodoh untuk ini adalah:

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

Namun, ini tidak menunjukkan makedengan cara apa pun bahwa kedua target akan dibuat secara bersamaan. Tidak apa-apa saat dijalankan makedalam serial, tetapi kemungkinan akan menyebabkan masalah jika seseorang mencoba make -j16atau sesuatu yang sama gilanya.

Pertanyaannya adalah apakah ada cara untuk menulis aturan Makefile yang tepat untuk kasus seperti itu? Jelas, ini akan menghasilkan DAG, tetapi entah bagaimana panduan GNU make tidak menentukan bagaimana kasus ini dapat ditangani.

Menjalankan kode yang sama dua kali dan hanya menghasilkan satu hasil tidak mungkin dilakukan, karena penghitungannya membutuhkan waktu (pikirkan: jam). Mengeluarkan hanya satu file juga akan agak sulit, karena sering digunakan sebagai input ke GNUPLOT yang tidak tahu bagaimana menangani hanya sebagian kecil dari file data.

makesaurus
sumber
1
Automake memiliki dokumentasi tentang ini: gnu.org/software/automake/manual/html_node/…
Frank

Jawaban:

12

Saya akan menyelesaikannya sebagai berikut:

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

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

Dalam hal ini paralel make akan 'membuat serial' membuat a dan b tetapi karena membuat b tidak melakukan apapun maka tidak perlu waktu.

Peter Tillemans
sumber
16
Sebenarnya, ada masalah dengan ini. Jika file-b.outdibuat seperti yang dinyatakan dan kemudian dihapus secara misterius, maketidak akan dapat membuatnya kembali karena file-a.outmasih ada. Ada pemikiran?
makesaurus
Jika Anda menghapus secara misterius file-a.outmaka solusi di atas berfungsi dengan baik. Ini menyarankan peretasan: ketika hanya satu dari a atau b yang ada, maka dependensi harus diurutkan sedemikian rupa sehingga file yang hilang muncul sebagai output dari input.in. Beberapa $(widcard...)s, $(filter...)s, $(filter-out...)s dll, seharusnya sudah cukup. Ugh.
bobbogo
3
PS foo-binjelas akan membuat salah satu file terlebih dahulu (menggunakan resolusi mikrodetik), jadi Anda harus memastikan Anda memiliki urutan dependensi yang benar ketika kedua file ada.
bobbogo
2
@bobbogo baik itu, atau tambahkan touch file-a.outsebagai perintah kedua setelah foo-binpemanggilan.
Connor Harris
Berikut ini perbaikan potensial untuk ini: tambahkan touch file-b.outsebagai perintah kedua di aturan pertama dan duplikat perintah di aturan kedua. Jika salah satu file hilang atau lebih tua dari input.in, perintah hanya akan dijalankan sekali dan akan membuat ulang kedua file dengan file-b.outlebih baru dari file-a.out.
chqrlie
128

Triknya adalah dengan menggunakan aturan pola dengan banyak target. Dalam kasus tersebut make akan mengasumsikan bahwa kedua target dibuat dengan satu pemanggilan perintah.

semua: file-a.out file-b.out
file-a% keluar file-b% keluar: input.in
    foo-bin input.in file-a $ * out file-b $ * out

Perbedaan interpretasi antara aturan pola dan aturan normal ini tidak sepenuhnya masuk akal, tetapi berguna untuk kasus seperti ini, dan ini didokumentasikan di manual.

Trik ini dapat digunakan untuk sejumlah file keluaran selama namanya memiliki beberapa substring umum untuk %dicocokkan. (Dalam hal ini substring yang umum adalah ".")

anjing lambat
sumber
3
Ini sebenarnya tidak benar. Aturan Anda menentukan bahwa file yang cocok dengan pola ini akan dibuat dari input.in menggunakan perintah yang ditentukan, tetapi tidak ada yang mengatakan bahwa mereka dibuat secara bersamaan. Jika Anda benar-benar menjalankannya secara paralel, makeakan menjalankan perintah yang sama dua kali secara bersamaan.
makesaurus
47
Silakan mencobanya sebelum Anda mengatakan itu tidak berfungsi ;-) Buku panduan GNU make mengatakan: "Aturan pola mungkin memiliki lebih dari satu target. Tidak seperti aturan normal, ini tidak bertindak sebagai banyak aturan berbeda dengan prasyarat dan perintah yang sama. Jika aturan pola memiliki banyak target, pastikan bahwa perintah aturan bertanggung jawab untuk membuat semua target. Perintah dijalankan hanya sekali untuk membuat semua target. " gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog
4
Tetapi bagaimana jika saya memiliki input yang sama sekali berbeda ?, Katakan foo.in dan bar.src? Apakah aturan pola mendukung pencocokan pola kosong?
grwlf
2
@dcow: File keluaran hanya perlu berbagi substring (bahkan satu karakter, seperti ".", sudah cukup) belum tentu merupakan awalan. Jika tidak ada substring yang umum, Anda dapat menggunakan target perantara seperti yang dijelaskan oleh richard dan deemer. @grwlf: Hei, itu berarti "foo.in" dan "bar.src" dapat dilakukan: foo%in bar%src: input.in:-)
slowdog
1
Jika masing-masing file keluaran disimpan dalam variabel, resep dapat ditulis sebagai: $(subst .,%,$(varA) $(varB)): input.in(diuji dengan GNU make 4.1).
stefanct
55

Make tidak memiliki cara intuitif untuk melakukan ini, tetapi ada dua solusi yang layak.

Pertama, jika target yang terlibat memiliki batang yang sama, Anda dapat menggunakan aturan awalan (dengan GNU make). Artinya, jika Anda ingin memperbaiki aturan berikut:

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

Anda bisa menulisnya seperti ini:

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

(Menggunakan variabel pola-aturan $ *, yang merupakan singkatan dari bagian yang cocok dari pola)

Jika Anda ingin menjadi portabel untuk implementasi non-GNU Make atau jika file Anda tidak dapat dinamai agar sesuai dengan aturan pola, ada cara lain:

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

Ini memberitahu make bahwa input.in.intermediate tidak akan ada sebelum make dijalankan, jadi ketiadaannya (atau timestamp-nya) tidak akan menyebabkan foo-bin dijalankan secara palsu. Dan apakah file-a.out atau file-b.out atau keduanya sudah kedaluwarsa (relatif terhadap input.in), foo-bin hanya akan dijalankan satu kali. Anda dapat menggunakan .SECONDARY alih-alih .INTERMEDIATE, yang akan menginstruksikan agar TIDAK menghapus nama file hipotetis input.in.intermediate. Metode ini juga aman untuk build paralel.

Titik koma di baris pertama penting. Ini membuat resep kosong untuk aturan itu, sehingga Make tahu bahwa kami benar-benar akan memperbarui file-a.out dan file-b.out (terima kasih @siulkiulki dan orang lain yang menunjukkan hal ini)

deemer
sumber
7
Bagus, sepertinya pendekatan .INTERMEDIATE berfungsi dengan baik. Lihat juga artikel Automake yang menjelaskan masalahnya (tetapi mereka mencoba menyelesaikannya dengan cara portabel).
grwlf
3
Ini adalah jawaban terbaik yang saya lihat untuk ini. Yang juga menarik adalah target khusus lainnya: gnu.org/software/make/manual/html_node/Special-T Target.html
Arne Babenhauserheide
2
@deemer Ini tidak bekerja untuk saya di OSX. (GNU Make 3.81)
Jorge Bucaran
2
Saya telah mencoba ini, dan saya telah memperhatikan masalah halus dengan build paralel --- dependensi tidak selalu menyebar dengan benar ke seluruh target menengah (meskipun semuanya baik-baik saja dengan -j1build). Juga, jika makefile terputus dan membiarkan input.in.intermediatefile di sekitarnya menjadi sangat bingung.
David Diberikan
3
Ada kesalahan pada jawaban ini. Anda dapat memiliki build paralel yang berfungsi dengan sempurna. Anda hanya perlu mengubah file-a.out file-b.out: input.in.intermediateke file-a.out file-b.out: input.in.intermediate ;Lihat stackoverflow.com/questions/37873522/… untuk lebih jelasnya.
siulkilulki
10

Ini didasarkan pada jawaban kedua @ deemer yang tidak bergantung pada aturan pola, dan ini memperbaiki masalah yang saya alami dengan penggunaan solusi bersarang.

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

Saya akan menambahkan ini sebagai komentar pada jawaban @ deemer, tetapi saya tidak bisa karena saya baru saja membuat akun ini dan tidak memiliki reputasi apa pun.

Penjelasan: Resep kosong diperlukan agar Make dapat melakukan pembukuan yang tepat untuk menandai file-a.outdan file-b.outtelah dibangun kembali. Jika Anda memiliki target perantara lain yang bergantung padanya file-a.out, Make akan memilih untuk tidak membangun perantara luar, dengan mengklaim:

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

Setelah GNU Make 4.3 (19 Januari 2020), Anda dapat menggunakan "target eksplisit yang dikelompokkan". Ganti :dengan &:.

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

File NEWS dari GNU Make mengatakan:

Fitur baru: Target eksplisit yang dikelompokkan

Aturan pola selalu memiliki kemampuan untuk menghasilkan banyak target dengan satu permintaan resep. Sekarang mungkin untuk mendeklarasikan bahwa aturan eksplisit menghasilkan beberapa target dengan satu pemanggilan. Untuk menggunakan ini, ganti ":" token dengan "&:" di aturan. Untuk mendeteksi fitur ini cari 'grouped-target' dalam variabel khusus .FEATURES. Implementasi dikontribusikan oleh Kaz Kylheku <[email protected]>

kakkun61.dll
sumber
2

Beginilah cara saya melakukannya. Pertama, saya selalu memisahkan prasyarat dari resep. Kemudian dalam hal ini target baru untuk melakukan resep tersebut.

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

Pertama kali:
1. Kami mencoba membangun file-a.out, tetapi dummy-a-and-b.out perlu dilakukan terlebih dahulu, jadi buatlah menjalankan resep dummy-a-and-b.out.
2. Kami mencoba untuk membangun file-b.out, dummy-a-and-b.out up to date.

Waktu kedua dan selanjutnya:
1. Kami mencoba membangun file-a.out: melihat prasyarat, prasyarat normal mutakhir, prasyarat sekunder tidak ada, jadi diabaikan.
2. Kami mencoba membangun file-b.out: lihat prasyarat, prasyarat normal mutakhir, prasyarat sekunder tidak ada, jadi diabaikan.

ctrl-alt-delor
sumber
1
Ini rusak. Jika Anda menghapus file-b.outmake tidak ada cara untuk membuatnya kembali.
bobbogo
menambahkan semua: file-a.out file-b.out sebagai aturan pertama. Seolah-olah file-b.out telah dihapus, dan make dijalankan tanpa target, pada baris perintah, itu tidak akan membangunnya kembali.
ctrl-alt-delor
@bobbogo Fixed: lihat komentar di atas.
ctrl-alt-delor
0

Sebagai perpanjangan dari jawaban @ deemer, saya telah menggeneralisasikannya menjadi sebuah fungsi.

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

Kerusakan:

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

Hapus spasi di awal / akhir dari argumen pertama

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

Buat target variabel ke nilai unik untuk digunakan sebagai target perantara. Untuk kasus ini, nilainya adalah .inter.file-a.out_file-b.out.

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

Buat resep kosong untuk keluaran dengan target unik sebagai ketergantungan.

$(eval .INTERMEDIATE: $(target) ) 

Nyatakan target unik sebagai perantara.

$(target)

Akhiri dengan referensi ke target unik sehingga fungsi ini dapat digunakan secara langsung dalam resep.

Perhatikan juga penggunaan eval di sini karena eval berkembang menjadi tidak ada sehingga perluasan fungsi sepenuhnya hanyalah target unik.

Harus memberikan kredit pada Aturan Atom di GNU Make yang diinspirasikan dari fungsi ini.

Lewis R
sumber
0

Untuk mencegah beberapa eksekusi paralel dari aturan dengan beberapa keluaran dengan make -jN, gunakan .NOTPARALLEL: outputs. Dalam kasus Anda:

.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
guilloptero
sumber