Programing

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

lottogame 2020. 8. 26. 08:21
반응형

단일 소스 파일에서 몇 개의 타겟을 생성하는 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'.

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

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

The first time:
1. We try to build file-a.out, but dummy-a-and-b.out needs doing first so make runs the dummy-a-and-b.out recipe.
2. We try to build file-b.out, dummy-a-and-b.out is up to date.

The second and subsequent time:
1. We try to build file-a.out: make looks at prerequisites, normal prerequisites are up to date, secondary prerequisites are missing so ignored.
2. We try to build file-b.out: make looks at prerequisites, normal prerequisites are up to date, secondary prerequisites are missing so ignored.


As an extension to @deemer's answer, I have generalised it into a function.

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

Breakdown:

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

Strip any leading/trailing whitespace from the first argument

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

Create a variable target to a unique value to use as the intermediate target. For this case, the value will be .inter.file-a.out_file-b.out.

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

Create an empty recipe for the outputs with the unique target as a depedency.

$(eval .INTERMEDIATE: $(target) ) 

Declare the unique target as an intermediate.

$(target)

Finish with a reference to the unique target so this function can be used directly in a recipe.

Also note the use of eval here is because eval expands to nothing so the full expansion of the function is just the unique target.

Must give credit to Atomic Rules in GNU Make which this function is inspired from.

참고URL : https://stackoverflow.com/questions/2973445/gnu-makefile-rule-generating-a-few-targets-from-a-single-source-file

반응형