TL;DR

  • 재귀 레시피에서 make를 직접 호출하면 병렬 작업 서버(jobserver)가 자식 프로세스에 전달되지 않을 수 있으며, GNU Make 3.81~4.3의 동작은 호출 방식과 -j 조합에 따라 달라짐.
  • 레시피 앞에 +를 붙이거나 make를 $(MAKE)로 바꾸면 작업 서버 전달과 호출한 make 바이너리 사용 문제를 해결할 수 있음.
  • 자식 레시피에 별도의 -jN을 지정하면 경고와 함께 작업 서버 모드가 비활성화되거나 재설정되는 경우가 있음.
  • 자식 레시피에서 -j만 지정한 행은 GNU Make 4.2.1·4.3에서 상위 -j 설정과 무관하게 무제한 병렬 실행으로 바뀜.
  • 상위와 하위의 병렬성 제한을 기대한 방식으로 조합하는 이상적인 동작은 제시된 버전에서 구현되지 않아, 호출 방식별 절충이 필요함.

재귀적 `make`와 병렬 실행

  • 재귀적 make는 Makefile 레시피가 다시 make를 실행하는 방식임.
  • GNU Make 공식 문서는 관련 내용을 잘 설명하지만, 여러 버전과 함께 작업하면서 GNU Make 3.81~4.3 사이의 동작 변화를 살펴봄.
  • 다음과 같이 레시피에서 make를 직접 실행하고 make -j4 ser를 호출하면, 최근 버전에서 jobserver unavailable: using -j1 경고가 나타날 수 있음.
  • ser 레시피는 make -C subdir을 실행함.
  • 작업 서버, 즉 -j 설정이 자식 make에 전달되지 않아 의도한 병렬성이 사라짐.
  • 호출한 것과 같은 make 바이너리를 사용한다는 보장도 없으며, PATH에서 발견되는 make가 실행됨.
  • 레시피 줄 앞에 +를 붙이거나 make를 $(MAKE)로 바꾸면 작업 서버 전달, 병렬 실행, 호출한 make 바이너리 사용 문제를 해결할 수 있음.
  • +를 붙이는 방식은 특별한 동작을 하는 기호라 마법처럼 느껴질 수 있으며, $(MAKE)를 쓰는 방식도 내부적으로는 마찬가지로 특별한 처리에 의존함.

실험 구성

  • 상위 Makefile에는 다음 호출 형태를 비교하는 타깃을 둠.
  • ser: make -C subdir
  • par2: make -j2 -C subdir
  • par4: make -j4 -C subdir
  • par: make -j -C subdir
  • 위 네 타깃의 +make 버전인 serplus, par2plus, par4plus, parplus.
  • 위 네 타깃의 $(MAKE) 버전인 sermake, par2make, par4make, parmake.
  • 하위 Makefile은 all 타깃에서 one부터 five까지 다섯 개의 작업을 실행하며, 각 작업은 이름을 출력하고 1초 대기한 뒤 대문자 이름을 출력함.
  • 여러 GNU Make 버전에서 make ser, make -j2 ser 등의 명령을 실행하고 동시에 수행되는 레시피 수를 비교함.
  • 표의 b는 하위 make가 -jN을 강제하면서 작업 서버 모드를 비활성화한다는 경고를 뜻함.
  • 예: make -j2 par2 실행 시 -jN forced in submake: disabling jobserver mode 경고.
  • 표의 a는 하위 make가 작업 서버를 사용할 수 없어 -j1로 동작한다는 경고를 뜻함.
  • 예: make -j2 ser 실행 시 jobserver unavailable: using -j1. Add '+' to parent make rule. 경고.

GNU Make 3.81

  • 각 값은 동시에 실행되는 레시피 수이며, ∞는 무제한 병렬 실행을 나타냄.
  • ser†, par†, parplus†, parmake†는 테스트한 Make 버전 간 차이가 나타난 비이식적 방식임.
  • ser†: 상위 명령의 -j 설정에 따라 1, 2, 4, ∞.
  • serplus, sermake: 1, 2, 4, ∞.
  • par2, par2plus, par2make: 2, 2b, 2b, 2.
  • par4, par4plus, par4make: 4, 4b, 4b, 4.
  • par†, parplus†, parmake†: ∞, 2, 4, ∞.
  • †로 표시된 비이식적 관용구는 실무에서 피하는 편이 좋다는 의견임.

GNU Make 4.0

  • 테스트 환경은 Debian 8.11임.
  • ser†: 상위 명령의 -j 설정에 따라 1, 1a, 1a, ∞.
  • serplus, sermake: 1, 2, 4, ∞.
  • par2, par2plus, par2make: 2, 2b, 2b, 2.
  • par4, par4plus, par4make: 4, 4b, 4b, 4.
  • par†: ∞, 1a, 1a, ∞.
  • parplus†, parmake†: ∞, 2, 4, ∞.
  • a 경고는 부모 레시피 앞에 +를 추가하라고 안내하며, b 경고는 하위 -jN 지정으로 작업 서버 모드가 비활성화됨을 알림.

GNU Make 4.2.1과 4.3

  • 테스트 환경은 Rocky 8.10의 GNU Make 4.2.1, Red Hat 9 또는 Ubuntu 22.04의 GNU Make 4.3임.
  • -jN forced in submake 경고 문구에 지정된 N 값이 포함되도록 바뀜.
  • make -j2 par에서는 -j0 forced in submake: resetting jobserver mode 경고.
  • make -j2 par2에서는 -j2 forced in submake: resetting jobserver mode 경고.
  • ser†: 1, 1a, 1a, ∞.
  • serplus, sermake: 1, 2, 4, ∞.
  • par2, par2plus, par2make: 2, 2b, 2b, 2.
  • par4, par4plus, par4make: 4, 4b, 4b, 4.
  • par†, parplus†, parmake†는 모두 ∞, ∞b, ∞b, ∞로 동작함.
  • 마지막 세 행은 GNU Make 3.81·4.0과 비교해 상위 명령이 -j2 또는 -j4여도 무제한 병렬 실행이 되는 변화임.

재귀 호출 방식의 절충

  • ser의 a 경고는 레시피 줄 앞에 +를 붙이면 사라짐.
  • 예: serplus에서 +make -C subdir을 실행함.
  • + 방식은 @나 - 접두사처럼 특별한 의미를 가진 기호에 의존한다는 점에서 마법처럼 느껴지는 해결책임.
  • 레시피 앞의 마법 같은 -보다 줄 끝의 || true를 선호한다는 견해임.
  • $(MAKE)를 사용하는 방식도 내부적으로는 특별 처리에 의존하지만, 겉보기에는 덜 마법적인 대안임.
  • 예: sermake에서 $(MAKE) -C subdir을 실행함.
  • +와 $(MAKE) 어느 쪽도 resetting jobserver mode 관련 b 경고를 없애지 못함.
  • 기대하는 이상적인 동작은 상위 명령이 -j2이면 병렬 작업 수가 2, -j4이면 4, -j이면 무제한이 되는 방식임.
  • 현재는 표에 인용된 호출 방식 중 선호하는 절충안을 선택해야 하는 상황임.
  • 게시일은 2026-09-30이며, 분류는 concurrency, makefiles, sre임.