단일 소스 파일에서 몇 개의 타겟을 생성하는 GNU Makefile 규칙 받아 두 개의 출력 파일을 생성 하는

다음을 시도하고 있습니다. 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 make 매뉴얼은이 경우를 처리하는 방법을 지정하지 않습니다.

동일한 코드를 두 번 실행하고 하나의 결과 만 생성하는 것은 계산에 시간이 걸리기 때문에 문제가되지 않습니다 (생각 : 몇 시간). 데이터 파일의 일부만 처리하는 방법을 모르는 GNUPLOT의 입력으로 자주 사용되기 때문에 하나의 파일 만 출력하는 것도 다소 어려울 수 있습니다.



답변

다음과 같이 해결할 것입니다.

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

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

이 경우 병렬 make는 a와 b를 만드는 ‘직렬화’하지만 b를 만드는 것은 아무 작업도하지 않기 때문에 시간이 걸리지 않습니다.


답변

트릭은 여러 대상이있는 패턴 규칙을 사용하는 것입니다. 이 경우 make는 명령을 한 번 호출하여 두 대상을 모두 생성한다고 가정합니다.

모두 : 파일 -a.out 파일 -b.out
파일 -a % out 파일 -b % out : input.in
    foo-bin input.in 파일 -a $ * out 파일 -b $ * out

패턴 규칙과 일반 ​​규칙 간의 이러한 해석 차이는 정확히 말이되지 않지만 이와 같은 경우에 유용하며 매뉴얼에 문서화되어 있습니다.

이 트릭은 이름에 공통 부분 문자열이있는 한 여러 출력 파일에 사용할 수 있습니다. % 에 일치 할 . (이 경우 공통 하위 문자열은 “.”)


답변

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

이것은 make가 실행되기 전에 input.in.intermediate가 존재하지 않을 것이라는 것을 알려줍니다. 그래서 그것의 부재 (또는 타임 스탬프)는 foo-bin이 가짜로 실행되는 원인이되지 않습니다. 그리고 file-a.out 또는 file-b.out 또는 둘 다 오래된 것이 든 (input.in에 상대적), foo-bin은 한 번만 실행됩니다. .INTERMEDIATE 대신 .SECONDARY를 사용할 수 있으며, 이는 make NOT이 가상 파일 이름 input.in.intermediate를 삭제하도록 지시합니다. 이 방법은 병렬 메이크 빌드에도 안전합니다.

첫 번째 줄의 세미콜론이 중요합니다. 해당 규칙에 대한 빈 레시피를 생성하므로 Make가 실제로 file-a.out 및 file-b.out을 업데이트 할 것임을 알 수 있습니다 (@siulkiulki 및이를 지적한 다른 사람들에게 감사드립니다).


답변

이것은 패턴 규칙에 의존하지 않는 @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.out경우 Make는 다음을 주장하면서 외부 중간을 빌드하지 않도록 선택합니다.

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


답변

GNU Make 4.3 (2020 년 1 월 19 일) 이후에는 “그룹화 된 명시 적 대상”을 사용할 수 있습니다. 교체 :와 함께 &:.

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

GNU Make의 NEWS 파일은 다음과 같이 말합니다.

새로운 기능 : 그룹화 된 명시 적 대상

패턴 규칙은 항상 레시피를 한 번만 호출하여 여러 대상을 생성 할 수있었습니다. 이제 명시 적 규칙이 단일 호출로 여러 대상을 생성한다고 선언 할 수 있습니다. 이를 사용하려면 규칙에서 “:”토큰을 “& :”로 바꾸십시오. 이 기능을 감지하려면 .FEATURES 특수 변수에서 ‘grouped-target’을 검색하십시오. 구현 제공 : Kaz Kylheku <kaz@kylheku.com>


답변

이것이 내가하는 방법이다. 먼저 저는 항상 사전 요청과 레시피를 분리합니다. 그런 다음이 경우 레시피를 수행 할 새 대상입니다.

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. 우리는 파일 a.out을 빌드하려고하지만, dummy-a-and-b.out은 먼저해야하므로 make는 dummy-a-and-b.out 레시피를 실행합니다.
2. 우리는 file-b.out을 빌드하려고합니다. dummy-a-and-b.out은 최신 상태입니다.

두 번째 및 그 이후 :
1. 우리는 file-a.out을 빌드하려고합니다. 전제 조건을 살펴보고 일반 전제 조건이 최신 상태이며 보조 전제 조건이 누락되어 무시됩니다.
2. file-b.out을 빌드하려고합니다. 전제 조건을 확인하고, 일반 전제 조건이 최신 상태이며, 보조 전제 조건이 누락되어 무시됩니다.


답변

@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) ;)

고유 한 대상을 종속성으로 사용하여 출력에 대한 빈 레시피를 만듭니다.

$(eval .INTERMEDIATE: $(target) )

고유 한 대상을 중간으로 선언하십시오.

$(target)

이 기능을 레시피에서 직접 사용할 수 있도록 고유 한 대상에 대한 참조로 완료합니다.

또한 여기서 eval을 사용하는 것은 eval이 아무것도 확장하지 않기 때문에 함수의 전체 확장이 유일한 대상이되기 때문입니다.

이 함수에서 영감을 얻은 GNU Make의 Atomic Rules에 기여해야 합니다.