กฎ GNU Makefile สร้างเป้าหมายสองสามรายการจากไฟล์ต้นฉบับเดียว


106

ฉันกำลังพยายามทำสิ่งต่อไปนี้ มีโปรแกรมหนึ่งเรียกมันfoo-binว่าใช้ในอินพุตไฟล์เดียวและสร้างไฟล์เอาต์พุตสองไฟล์ กฎ Makefile โง่สำหรับสิ่งนี้คือ:

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

อย่างไรก็ตามสิ่งนี้ไม่ได้บอกmakeแต่อย่างใดว่าเป้าหมายทั้งสองจะถูกสร้างขึ้นพร้อมกัน เป็นเรื่องปกติเมื่อทำงานmakeแบบอนุกรม แต่อาจทำให้เกิดปัญหาหากมีใครพยายามmake -j16หรือทำอะไรที่บ้าบอพอ ๆ กัน

คำถามคือมีวิธีเขียนกฎ Makefile ที่เหมาะสมสำหรับกรณีดังกล่าวหรือไม่? เห็นได้ชัดว่ามันจะสร้าง DAG แต่อย่างใดคู่มือการสร้าง GNU ไม่ได้ระบุวิธีจัดการกรณีนี้

การรันโค้ดเดียวกันสองครั้งและสร้างผลลัพธ์เพียงผลลัพธ์เดียวนั้นไม่เป็นปัญหาเนื่องจากการคำนวณต้องใช้เวลา (คิดว่า: ชั่วโมง) การส่งออกไฟล์เพียงไฟล์เดียวก็ค่อนข้างยากเช่นกันเนื่องจากมักใช้เป็นอินพุตไปยัง GNUPLOT ซึ่งไม่ทราบวิธีจัดการไฟล์ข้อมูลเพียงเศษเสี้ยว


1
Automake มีเอกสารเกี่ยวกับเรื่องนี้: gnu.org/software/automake/manual/html_node/…
Frank

คำตอบ:


12

ฉันจะแก้ปัญหาดังนี้:

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

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

ในกรณีนี้การสร้างแบบขนานจะ 'ทำให้เป็นอนุกรม' การสร้าง a และ b แต่เนื่องจากการสร้าง b ไม่ได้ทำอะไรเลยจึงใช้เวลาไม่นาน


16
จริงๆแล้วมีปัญหากับเรื่องนี้ หากfile-b.outสร้างตามที่ระบุไว้แล้วลบออกอย่างลึกลับmakeจะไม่สามารถสร้างขึ้นใหม่ได้เนื่องจากfile-a.outจะยังคงมีอยู่ ความคิดใด ๆ ?
makeaurus

หากคุณลบอย่างลึกลับfile-a.outวิธีแก้ปัญหาข้างต้นก็ใช้ได้ดี สิ่งนี้บ่งบอกถึงการแฮ็ก: เมื่อมี a หรือ b เพียงตัวเดียวควรเรียงลำดับการอ้างอิงเพื่อให้ไฟล์ที่หายไปปรากฏเป็นผลลัพธ์ของinput.in. ไม่กี่$(widcard...)วินาที, $(filter...)s, $(filter-out...)s ฯลฯ ควรทำเคล็ดลับ ฮึ.
bobbogo

3
foo-binเห็นได้ชัดว่าPS จะสร้างไฟล์ขึ้นมาก่อน (โดยใช้ความละเอียดระดับไมโครวินาที) ดังนั้นคุณต้องแน่ใจว่าคุณมีลำดับการอ้างอิงที่ถูกต้องเมื่อมีทั้งสองไฟล์
bobbogo

2
@bobbogo อย่างใดอย่างหนึ่งหรือเพิ่มtouch file-a.outเป็นคำสั่งที่สองหลังจากการfoo-binเรียกใช้
Connor Harris

นี่คือการแก้ไขที่เป็นไปได้สำหรับสิ่งนี้: เพิ่มtouch file-b.outเป็นคำสั่งที่สองในกฎข้อแรกและทำซ้ำคำสั่งในกฎข้อที่สอง หากไฟล์อย่างใดอย่างหนึ่งที่ขาดหายไปแก่กว่าหรือinput.inคำสั่งจะถูกดำเนินการเพียงครั้งเดียวและจะสร้างไฟล์ทั้งที่มีที่ผ่านมามากกว่าfile-b.out file-a.out
chqrlie

128

เคล็ดลับคือการใช้กฎรูปแบบกับหลายเป้าหมาย ในกรณีนั้น make จะถือว่าเป้าหมายทั้งสองถูกสร้างขึ้นโดยการเรียกใช้คำสั่งเดียว

ทั้งหมด: file-a.out file-b.out
file-a% out file-b% out: input.in
    foo-bin input.in file-a $ * out file-b $ * out

ความแตกต่างในการตีความระหว่างกฎรูปแบบและกฎปกติไม่สมเหตุสมผล แต่มีประโยชน์สำหรับกรณีเช่นนี้และมีการบันทึกไว้ในคู่มือ

เคล็ดลับนี้สามารถใช้ได้กับไฟล์เอาต์พุตจำนวนเท่าใดก็ได้ตราบเท่าที่ชื่อของพวกเขามีสตริงย่อยทั่วไปสำหรับการ%จับคู่ (ในกรณีนี้สตริงย่อยทั่วไปคือ ".")


3
สิ่งนี้ไม่ถูกต้อง กฎของคุณระบุว่าไฟล์ที่ตรงกับรูปแบบเหล่านี้ต้องสร้างจาก input.in โดยใช้คำสั่งที่ระบุ แต่ไม่มีที่ไหนบอกว่าสร้างพร้อมกัน หากคุณเรียกใช้แบบขนานจริงmakeจะเรียกใช้คำสั่งเดียวกันสองครั้งพร้อมกัน
makeaurus

47
โปรดทดลองใช้ก่อนที่คุณจะบอกว่ามันใช้ไม่ได้ ;-) คู่มือการจัดทำ GNU ระบุว่า: "กฎของรูปแบบอาจมีมากกว่าหนึ่งเป้าหมายซึ่งแตกต่างจากกฎทั่วไปกฎนี้ไม่ได้ทำหน้าที่เป็นกฎที่แตกต่างกันมากนักโดยมีข้อกำหนดเบื้องต้นและคำสั่งเดียวกันหาก กฎรูปแบบมีหลายเป้าหมายทำให้รู้ว่าคำสั่งของกฎมีหน้าที่ในการสร้างเป้าหมายทั้งหมดคำสั่งจะดำเนินการเพียงครั้งเดียวเพื่อสร้างเป้าหมายทั้งหมด " gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog

4
แต่ถ้าฉันมีอินพุตที่ต่างกันโดยสิ้นเชิงล่ะพูดว่า foo.in และ bar.src? กฎของรูปแบบรองรับรูปแบบที่ว่างเปล่าหรือไม่
grwlf

2
@dcow: ไฟล์เอาต์พุตจำเป็นต้องแชร์สตริงย่อยเท่านั้น (แม้แต่อักขระตัวเดียวเช่น "." ก็เพียงพอแล้ว) ไม่จำเป็นต้องเป็นคำนำหน้า หากไม่มีสตริงย่อยทั่วไปคุณสามารถใช้เป้าหมายระดับกลางตามที่ richard และ deemer อธิบายได้ @grwlf: เฮ้นั่นหมายความว่า "foo.in" และ "bar.src" สามารถทำได้: foo%in bar%src: input.in:-)
slowdog

1
หากไฟล์เอาต์พุตตามลำดับถูกเก็บไว้ในตัวแปรสูตรสามารถเขียนเป็น: $(subst .,%,$(varA) $(varB)): input.in(ทดสอบด้วย GNU make 4.1)
stefanct

55

Make ไม่มีวิธีง่ายๆในการทำเช่นนี้ แต่มีวิธีแก้ปัญหาที่ดีสองวิธี

ขั้นแรกหากเป้าหมายที่เกี่ยวข้องมีลำต้นร่วมกันคุณสามารถใช้กฎคำนำหน้า (กับ GNU make) นั่นคือหากคุณต้องการแก้ไขกฎต่อไปนี้:

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

คุณสามารถเขียนด้วยวิธีนี้:

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

(โดยใช้ตัวแปรกฎรูปแบบ $ * ซึ่งย่อมาจากส่วนที่ตรงกันของรูปแบบ)

หากคุณต้องการพกพาไปใช้งานที่ไม่ใช่ GNU Make หรือถ้าไฟล์ของคุณไม่สามารถตั้งชื่อให้ตรงกับกฎรูปแบบมีอีกวิธีหนึ่ง:

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

สิ่งนี้จะบอกว่า input.in.intermediate จะไม่มีอยู่ก่อนที่จะรัน make ดังนั้นการไม่มี (หรือการประทับเวลา) จะไม่ทำให้ foo-bin ทำงานผิดพลาด และไม่ว่า file-a.out หรือ file-b.out หรือทั้งคู่จะล้าสมัย (เทียบกับ input.in) foo-bin จะทำงานเพียงครั้งเดียว คุณสามารถใช้. ชั้นมัธยมศึกษาแทน. INTERMEDIATE ซึ่งจะสั่งให้อย่าลบชื่อไฟล์สมมุติ input.in.intermediate วิธีนี้ยังปลอดภัยสำหรับการสร้างแบบขนาน

อัฒภาคในบรรทัดแรกมีความสำคัญ สร้างสูตรว่างสำหรับกฎนั้นเพื่อให้ Make รู้ว่าเราจะอัปเดต file-a.out และ file-b.out จริงๆ (ขอบคุณ @siulkiulki และคนอื่น ๆ ที่ชี้ให้เห็น)


7
ดีดูเหมือนว่าวิธีการ. INTERMEDIATE ทำงานได้ดี ดูบทความ Automake ที่ อธิบายปัญหา (แต่พวกเขาพยายามแก้ไขด้วยวิธีพกพา)
grwlf

3
นี่คือคำตอบที่ดีที่สุดที่ฉันเห็น สิ่งที่น่าสนใจอาจเป็นเป้าหมายพิเศษอื่น ๆ : gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide

2
@deemer นี่ใช้ไม่ได้สำหรับฉันบน OSX (GNU Make 3.81)
Jorge Bucaran

2
ฉันได้ลองใช้สิ่งนี้และสังเกตเห็นปัญหาเล็กน้อยเกี่ยวกับการสร้างแบบขนาน --- การอ้างอิงไม่ได้แพร่กระจายอย่างถูกต้องในเป้าหมายระดับกลางเสมอไป (แม้ว่าทุกอย่างจะเป็นไปด้วยดี-j1ก็ตาม) นอกจากนี้หาก makefile ถูกขัดจังหวะและปล่อยให้input.in.intermediateไฟล์รอบ ๆ ไฟล์สับสนจริงๆ
David Given

3
คำตอบนี้มีข้อผิดพลาด คุณสามารถสร้างแบบขนานที่ทำงานได้อย่างสมบูรณ์แบบ คุณเพียงแค่ต้องเปลี่ยนfile-a.out file-b.out: input.in.intermediateเป็นfile-a.out file-b.out: input.in.intermediate ;ดูstackoverflow.com/questions/37873522/…สำหรับรายละเอียดเพิ่มเติม
siulkilulki

10

นี่เป็นไปตามคำตอบที่สองของ @ deemer ซึ่งไม่ขึ้นอยู่กับกฎรูปแบบและจะแก้ไขปัญหาที่ฉันพบด้วยการใช้วิธีแก้ปัญหาแบบซ้อนกัน

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

ฉันจะเพิ่มสิ่งนี้เป็นความคิดเห็นในคำตอบของ @ deemer แต่ทำไม่ได้เพราะฉันเพิ่งสร้างบัญชีนี้และไม่มีชื่อเสียงใด ๆ

คำอธิบาย: จำเป็นต้องใช้สูตรอาหารเปล่าเพื่อให้ Make ทำบัญชีที่เหมาะสมเพื่อทำเครื่องหมายfile-a.outและfile-b.outสร้างขึ้นใหม่ หากคุณยังมีเป้าหมายระดับกลางอื่นซึ่งขึ้นอยู่กับfile-a.outMake จะเลือกที่จะไม่สร้างตัวกลางด้านนอกโดยอ้างว่า:

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

10

หลังจาก GNU Make 4.3 (19 มกราคม 2020) คุณสามารถใช้ "เป้าหมายที่ชัดเจนแบบจัดกลุ่ม" ได้ แทนที่:ด้วย&:.

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

ไฟล์ NEWS ของ GNU Make พูดว่า:

คุณลักษณะใหม่: จัดกลุ่มเป้าหมายที่ชัดเจน

กฎรูปแบบมีความสามารถในการสร้างเป้าหมายหลาย ๆ เป้าหมายด้วยการเรียกใช้สูตรอาหารเพียงครั้งเดียว ตอนนี้เป็นไปได้ที่จะประกาศว่ากฎที่ชัดเจนสร้างเป้าหมายหลายเป้าหมายด้วยการเรียกใช้ครั้งเดียว ในการใช้สิ่งนี้ให้แทนที่โทเค็น ":" ด้วย "&:" ในกฎ ในการตรวจจับคุณสมบัตินี้ให้ค้นหา 'grouped-target' ในตัวแปรพิเศษ. FEATURES การนำไปใช้งานโดย Kaz Kylheku <kaz@kylheku.com>


2

นี่คือวิธีที่ฉันทำ ก่อนอื่นฉันมักจะแยกข้อกำหนดเบื้องต้นออกจากสูตรอาหาร จากนั้นในกรณีนี้เป้าหมายใหม่ในการทำสูตร

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

ครั้งแรก:
1. เราพยายามสร้าง file-a.out แต่ต้องทำ dummy-a-and-b.out ก่อนเพื่อให้รันสูตร dummy-a-and-b.out
2. เราพยายามสร้าง file-b.out, dummy-a-and-b.out เป็นเวอร์ชันล่าสุด

ครั้งที่สองและครั้งต่อ ๆ ไป:
1. เราพยายามสร้าง file-a.out: ดูข้อกำหนดเบื้องต้นข้อกำหนดเบื้องต้นตามปกติเป็นข้อมูลล่าสุดข้อกำหนดเบื้องต้นรองขาดหายไปจึงถูกเพิกเฉย
2. เราพยายามสร้าง file-b.out: ดูข้อกำหนดเบื้องต้นข้อกำหนดเบื้องต้นตามปกติเป็นข้อมูลล่าสุดข้อกำหนดเบื้องต้นรองขาดหายไปจึงถูกเพิกเฉย


1
นี่เสียแล้ว หากคุณลบfile-b.outmake จะไม่มีทางสร้างใหม่ได้
bobbogo

เพิ่มทั้งหมด: file-a.out file-b.out เป็นกฎข้อแรก ราวกับว่า file-b.out ถูกลบออกและ make ถูกรันโดยไม่มีเป้าหมายในบรรทัดคำสั่งก็จะไม่สร้างขึ้นมาใหม่
ctrl-alt-delor

@bobbogo คงที่: ดูความคิดเห็นด้านบน
ctrl-alt-delor

0

ในฐานะที่เป็นส่วนขยายของคำตอบของ @ deemer ฉันได้สรุปให้เป็นฟังก์ชัน

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

ทำให้พังถล่ม:

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

ตัดช่องว่างนำหน้า / ต่อท้ายจากอาร์กิวเมนต์แรก

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

สร้างเป้าหมายตัวแปรเป็นค่าเฉพาะเพื่อใช้เป็นเป้าหมายระดับกลาง สำหรับกรณีนี้ค่าจะเป็น. inter.file-a.out_file-b.out

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

สร้างสูตรว่างสำหรับผลลัพธ์โดยมีเป้าหมายที่ไม่ซ้ำกันเป็น Depedency

$(eval .INTERMEDIATE: $(target) ) 

ประกาศเป้าหมายที่ไม่ซ้ำกันเป็นตัวกลาง

$(target)

จบด้วยการอ้างอิงถึงเป้าหมายเฉพาะเพื่อให้สามารถใช้ฟังก์ชันนี้ในสูตรอาหารได้โดยตรง

นอกจากนี้โปรดทราบว่าการใช้ eval ที่นี่เป็นเพราะ eval ขยายไปไม่มีอะไรดังนั้นการขยายฟังก์ชันทั้งหมดจึงเป็นเพียงเป้าหมายที่ไม่ซ้ำกัน

ต้องยกเครดิตให้กับAtomic Rules ใน GNU Makeซึ่งฟังก์ชันนี้ได้รับแรงบันดาลใจมาจาก


-1

เพื่อป้องกันไม่ให้การดำเนินการหลายขนานของการปกครองที่มีผลหลายกับการใช้งานmake -jN .NOTPARALLEL: outputsในกรณีของคุณ:

.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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.