Quy tắc GNU Makefile tạo một vài mục tiêu từ một tệp nguồn duy nhất


105

Tôi đang cố gắng làm những điều sau đây. Có một chương trình, gọi là nó foo-bin, lấy một tệp đầu vào duy nhất và tạo ra hai tệp đầu ra. Một quy tắc Makefile ngu ngốc cho điều này sẽ là:

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

Tuy nhiên, điều này không cho biết makecả hai mục tiêu sẽ được tạo ra cùng một lúc. Điều đó tốt khi chạy makenối tiếp, nhưng có thể sẽ gây ra rắc rối nếu một người cố gắng make -j16hoặc một cái gì đó điên rồ tương tự.

Câu hỏi đặt ra là liệu có tồn tại cách viết quy tắc Makefile thích hợp cho trường hợp như vậy không? Rõ ràng, nó sẽ tạo ra một DAG, nhưng bằng cách nào đó, hướng dẫn tạo GNU không chỉ rõ cách xử lý trường hợp này.

Chạy cùng một đoạn mã hai lần và chỉ tạo ra một kết quả là điều không cần thiết, vì việc tính toán mất nhiều thời gian (hãy nghĩ: hàng giờ). Việc xuất ra chỉ một tệp cũng sẽ khá khó khăn, vì nó thường được sử dụng làm đầu vào cho GNUPLOT mà không biết cách xử lý chỉ một phần nhỏ của tệp dữ liệu.


1
Automake có tài liệu về điều này: gnu.org/software/automake/manual/html_node/…
Frank

Câu trả lời:


12

Tôi sẽ giải quyết nó như sau:

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

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

Trong trường hợp này, song song make sẽ 'tuần tự hóa' tạo a và b nhưng vì việc tạo b không thực hiện bất cứ điều gì nên không mất thời gian.


16
Trên thực tế, có một vấn đề với điều này. Nếu file-b.outđã được tạo như đã nêu và sau đó bị xóa một cách bí ẩn, makesẽ không thể tạo lại nó vì nó file-a.outvẫn còn. Có suy nghĩ gì không?
makeaurus

Nếu bạn xóa một cách bí ẩn file-a.outthì giải pháp trên hoạt động tốt. Điều này cho thấy một sự cố: khi chỉ tồn tại một trong số a hoặc b, thì các phụ thuộc phải được sắp xếp sao cho tệp bị thiếu xuất hiện dưới dạng đầu ra của input.in. Một vài $(widcard...)s, s, $(filter...)s, $(filter-out...)v.v., sẽ thực hiện thủ thuật. Ặc.
bobbogo

3
PS foo-binrõ ràng sẽ tạo một trong các tệp trước (sử dụng độ phân giải micro giây), vì vậy bạn phải đảm bảo bạn có thứ tự phụ thuộc chính xác khi cả hai tệp đều tồn tại.
bobbogo

2
@bobbogo hoặc là lệnh đó hoặc thêm touch file-a.outdưới dạng lệnh thứ hai sau foo-binlời gọi.
Connor Harris

Đây là một bản sửa lỗi tiềm năng cho điều này: thêm touch file-b.outdưới dạng lệnh thứ hai trong quy tắc đầu tiên và sao chép các lệnh trong quy tắc thứ hai. Nếu một trong hai tệp bị thiếu hoặc cũ hơn input.in, lệnh sẽ được thực thi một lần duy nhất và sẽ tạo lại cả hai tệp có tệp file-b.outmới hơn file-a.out.
chqrlie

128

Bí quyết là sử dụng quy tắc mẫu với nhiều mục tiêu. Trong trường hợp đó, make sẽ giả định rằng cả hai mục tiêu đều được tạo ra bởi một lệnh gọi duy nhất.

tất cả: tệp-a.out tệp-b.out
file-a% out file-b% out: input.in
    foo-bin input.in file-a $ * out file-b $ * out

Sự khác biệt trong cách diễn giải giữa các quy tắc mẫu và quy tắc bình thường không chính xác có ý nghĩa, nhưng nó hữu ích cho những trường hợp như thế này và nó được ghi lại trong sách hướng dẫn.

Thủ thuật này có thể được sử dụng cho bất kỳ số lượng tệp đầu ra nào miễn là tên của chúng có một số chuỗi con chung %để khớp. (Trong trường hợp này, chuỗi con chung là ".")


3
Điều này thực sự không chính xác. Quy tắc của bạn chỉ định rằng các tệp phù hợp với các mẫu này phải được tạo từ input.in bằng cách sử dụng lệnh được chỉ định, nhưng không có nơi nào nói rằng chúng được tạo đồng thời. Nếu bạn thực sự chạy nó song song, makesẽ chạy cùng một lệnh hai lần đồng thời.
makeaurus

47
Vui lòng thử trước khi bạn nói rằng nó không hoạt động ;-) Hướng dẫn sử dụng GNU cho biết: "Các quy tắc mẫu có thể có nhiều hơn một mục tiêu. Không giống như các quy tắc thông thường, điều này không hoạt động như nhiều quy tắc khác nhau với cùng điều kiện tiên quyết và lệnh. Nếu một quy tắc mẫu có nhiều mục tiêu, hãy biết rằng các lệnh của quy tắc chịu trách nhiệm tạo tất cả các mục tiêu. Các lệnh chỉ được thực hiện một lần để tạo tất cả các mục tiêu. " gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog

4
Nhưng điều gì sẽ xảy ra nếu tôi có các đầu vào hoàn toàn khác nhau ?, Nói foo.in và bar.src? Quy tắc mẫu có hỗ trợ khớp mẫu trống không?
grwlf

2
@dcow: Các tệp đầu ra chỉ cần chia sẻ một chuỗi con (thậm chí một ký tự đơn lẻ, chẳng hạn như ".", là đủ) không nhất thiết phải là tiền tố. Nếu không có chuỗi con chung, bạn có thể sử dụng mục tiêu trung gian như được mô tả bởi richard và deemer. @grwlf: Này, điều đó có nghĩa là "foo.in" và "bar.src" có thể được thực hiện: foo%in bar%src: input.in:-)
slowdog

1
Nếu các tệp đầu ra tương ứng được giữ trong các biến, công thức có thể được viết là: $(subst .,%,$(varA) $(varB)): input.in(được thử nghiệm với GNU make 4.1).
stefanct

55

Make không có bất kỳ cách trực quan nào để thực hiện việc này, nhưng có hai cách giải quyết phù hợp.

Đầu tiên, nếu các mục tiêu liên quan có gốc chung, bạn có thể sử dụng quy tắc tiền tố (với GNU make). Đó là, nếu bạn muốn sửa quy tắc sau:

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

Bạn có thể viết nó theo cách này:

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

(Sử dụng biến quy tắc mẫu $ *, viết tắt của phần phù hợp của mẫu)

Nếu bạn muốn có thể di chuyển đến các triển khai không phải GNU hoặc nếu tệp của bạn không thể được đặt tên để phù hợp với quy tắc mẫu, có một cách khác:

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

Điều này cho biết rằng input.in.inter Instant sẽ không tồn tại trước khi make được chạy, vì vậy sự vắng mặt của nó (hoặc dấu thời gian của nó) sẽ không khiến foo-bin được chạy một cách nhanh chóng. Và cho dù tệp-a.out hoặc tệp-b.out hoặc cả hai đều đã lỗi thời (liên quan đến input.in), foo-bin sẽ chỉ được chạy một lần. Bạn có thể sử dụng .SECONDARY thay vì .INTERMEDIATE, sẽ hướng dẫn KHÔNG xóa tên tệp giả định input.in.inter Instant. Phương pháp này cũng an toàn cho các bản dựng song song.

Dấu chấm phẩy trên dòng đầu tiên là quan trọng. Nó tạo ra một công thức trống cho quy tắc đó, để Make biết rằng chúng tôi thực sự sẽ cập nhật tệp-a.out và tệp-b.out (cảm ơn @siulkiulki và những người khác đã chỉ ra điều này)


7
Tốt, có vẻ như phương pháp .INTERMEDIATE hoạt động tốt. Xem thêm bài viết Automake mô tả vấn đề (nhưng họ cố gắng giải quyết nó theo cách di động).
grwlf

3
Đây là câu trả lời tốt nhất tôi thấy cho điều này. Cũng quan tâm có thể là mục tiêu đặc biệt khác: gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide

2
@deemer Điều này không hoạt động đối với tôi trên OSX. (GNU Make 3.81)
Jorge Bucaran

2
Tôi đã thử điều này và tôi nhận thấy các vấn đề nhỏ với các bản dựng song song --- các phần phụ thuộc không phải lúc nào cũng lan truyền đúng mục tiêu trung gian (mặc dù mọi thứ đều ổn với các -j1bản dựng). Ngoài ra, nếu makefile bị gián đoạn và khiến input.in.intermediatetệp xung quanh nó thực sự bị nhầm lẫn.
David Given

3
Câu trả lời này có một lỗi. Bạn có thể có các bản dựng song song hoạt động hoàn hảo. Bạn chỉ cần thay đổi file-a.out file-b.out: input.in.intermediatethành file-a.out file-b.out: input.in.intermediate ;Xem stackoverflow.com/questions/37873522/… để biết thêm chi tiết.
siulkilulki

10

Điều này dựa trên câu trả lời thứ hai của @ deemer không dựa trên quy tắc mẫu và nó khắc phục sự cố mà tôi đã gặp phải với việc sử dụng lồng nhau của giải pháp.

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

Tôi đã thêm điều này làm bình luận cho câu trả lời của @ deemer, nhưng tôi không thể vì tôi mới tạo tài khoản này và không có bất kỳ danh tiếng nào.

Giải thích: Cần có công thức trống để cho phép Make thực hiện việc ghi sổ kế toán thích hợp để đánh dấu file-a.outfile-b.outnhư đã được xây dựng lại. Nếu bạn có một mục tiêu trung gian khác phụ thuộc vào file-a.out, thì Make sẽ chọn không tạo mục tiêu trung gian bên ngoài, tuyên bố:

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

9

Sau GNU Make 4.3 (ngày 19 tháng 1 năm 2020), bạn có thể sử dụng “mục tiêu rõ ràng được nhóm lại”. Thay thế :bằng &:.

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

Tệp NEWS của GNU Make cho biết:

Tính năng mới: Các mục tiêu rõ ràng được nhóm lại

Các quy tắc mẫu luôn có khả năng tạo nhiều mục tiêu chỉ với một lần gọi công thức. Bây giờ có thể tuyên bố rằng một quy tắc rõ ràng tạo ra nhiều mục tiêu với một lệnh gọi duy nhất. Để sử dụng điều này, hãy thay thế mã thông báo ":" bằng "&:" trong quy tắc. Để phát hiện tính năng này, hãy tìm kiếm 'mục tiêu theo nhóm' trong biến đặc biệt .FEATURES. Thực hiện do Kaz Kylheku <kaz@kylheku.com> đóng góp


2

Đây là cách tôi làm điều đó. Đầu tiên, tôi luôn tách biệt những điều kiện tiên quyết ra khỏi công thức nấu ăn. Sau đó, trong trường hợp này một mục tiêu mới để thực hiện công thức.

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

Lần đầu tiên:
1. Chúng tôi cố gắng xây dựng tệp-a.out, nhưng dummy-a-and-b.out cần làm trước để thực hiện chạy công thức dummy-a-and-b.out.
2. Chúng tôi cố gắng xây dựng tệp-b.out, dummy-a-and-b.out được cập nhật.

Lần thứ hai và lần sau:
1. Chúng tôi cố gắng xây dựng file-a.out: xem xét các điều kiện tiên quyết, điều kiện tiên quyết bình thường đã được cập nhật, điều kiện tiên quyết phụ bị thiếu nên bỏ qua.
2. Chúng tôi cố gắng xây dựng file-b.out: xem xét các điều kiện tiên quyết, điều kiện tiên quyết bình thường được cập nhật, điều kiện tiên quyết phụ bị thiếu nên bị bỏ qua.


1
Nó bị hỏng. Nếu bạn xóa file-b.outmake không có cách nào để tạo lại nó.
bobbogo

đã thêm tất cả: tệp-a.out tệp-b.out làm quy tắc đầu tiên. Như thể file-b.out bị xóa và make được chạy mà không có đích, trên dòng lệnh, nó sẽ không xây dựng lại nó.
ctrl-alt-delor

@bobbogo Đã sửa: xem nhận xét ở trên.
ctrl-alt-delor

0

Như một phần mở rộng cho câu trả lời của @ deemer, tôi đã khái quát nó thành một hàm.

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

Phá vỡ:

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

Loại bỏ bất kỳ khoảng trắng đầu / cuối nào khỏi đối số đầu tiên

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

Tạo một mục tiêu biến thành một giá trị duy nhất để sử dụng làm mục tiêu trung gian. Đối với trường hợp này, giá trị sẽ là .inter.file-a.out_file-b.out.

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

Tạo một công thức trống cho các kết quả đầu ra với mục tiêu duy nhất làm độ dốc.

$(eval .INTERMEDIATE: $(target) ) 

Khai báo mục tiêu duy nhất dưới dạng trung gian.

$(target)

Kết thúc bằng tham chiếu đến mục tiêu duy nhất để chức năng này có thể được sử dụng trực tiếp trong công thức.

Cũng lưu ý rằng việc sử dụng eval ở đây là vì eval mở rộng thành hư không nên việc mở rộng đầy đủ của hàm chỉ là mục tiêu duy nhất.

Phải cấp tín dụng cho Quy tắc nguyên tử trong GNU Make mà chức năng này được lấy cảm hứng từ.


0

Để ngăn thực thi song song nhiều quy tắc với nhiều đầu ra make -jN, hãy sử dụng .NOTPARALLEL: outputs. Trong trường hợp của bạn :

.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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.