Embedded Linux

Makefile 문법

작성자 임베디드코리아 작성일24-11-29 22:13 조회627회 댓글0건
■ 형식
    <Target> : <Dependencies>
        <Recipe>
    º Target : 빌드 대상 이름을 의미한다. 최종적으로 생성하는 파일명을 작성한다.
    º Dependencies : 빌드 대상이 의존하는 Target 이나 파일 목록이다. 여기에 나열된 대상들을 먼저 만들고 Recipe 의 명령어를 실행한다.
    º Recipe : 빌드 대상을 생성하는 명령이다. 여러 줄로 작성할 수 있고, 각 줄 시작에 반드시 Tab 문자로 Indent 가 삽입되어야 한다.

    @ : Shell 명령어를 실행하되, 실행하는 명령어를 출력하지 않을 때 사용한다.
        $ vi  Makefile
        hello :
      echo hello
    @echo this line printed only once
        $ make hello
        echo hello
        hello
        this line printed only once

■ Makefile 내장 규칙 (Built-in Rule)
    Make 에서 자주 사용되는 빌드 규칙들은 내장되어 있다. 대표적으로 소스 파일(*.c) 을 컴파일해서 오브젝트 파일(*.o)로 만드는 규칙이 있다.

  ------------------------------------------------------------------
    $ vi Makefile
    app.out : main.o foo.o bar.o
    gcc -o app.out main.o foo.o bar.o
    -----------------------------------------------------------------------------
    위와 같이 작성할 경우 Incremental build 를 위한 의존성 검사에서 헤더 파일의 변경을 감지하지 못하는 문제가 발생한다.
    make 는 소스 파일의 마지막 변경 시점만 확인하지, 소스 코드 내부의 변경 사항을 하나씩 대조하지 않기 때문이다.
    따라서 다음과 같이 각 Target 에 대한 Dependencies 까지 명시해주는 것이 바람직하다.
  -----------------------------------------------------------------------------------
  $ vi Makefile
    app.out : main.o foo.o bar.o
gcc -o gcc.out main.o foo.o bar.o

    main.o : foo.h bar.h main.c
foo.o : foo.h foo.c
bar.o : bar.h bar.c
    ------------------------------------------------------------------------
        위와 같이 작성하면 헤더 파일만 변경되어도 의존성이 올바르게 탐지된다. 마지막 세 줄에 있는 Target 의 Recipe 는 모두 생략되었지만, Make 내부 규칙에 의해 컴파일이 수행된다.

■ 변수 선언
    make 에서 변수를 선언하는 방법을 크게 2가지가 있다.
    Recursively expanded variable(반복 확장 변수), Simply expanded variable(단순 확장 변수)

(1) Recursively expanded variable
      = 기호를 사용해서 변수에 값을 할당하는 방법이다. 변수에 다른 변수를 참조하고 있다면, 다른 변수가 참조하고 있는 값을 참조한다.
      ---------------------------------------------
      foo = $(bar)
      bar = $(ugh)
      ugh = Huh?

      all:;echo $(foo)      <위의 실행결과로 Huh? 가 출력된다.>
    ----------------------------------------------------------------------------

    < 장점 >
    여러 변수를 사용해서 의도한 대로 make 를 작동 시킬 수 있다.
            CFLAGS = $(include_dirs) -O
            include_dirs = -lfoo -lbar
      --  변수 CFLAGS 에는 최종적으로 -lfoo -lbar -O 가 할당된다.

      < 단점 >
      변수 뒤에 다른 것을 추가할 수 없다. 이는 무한 반복에 빠지기 때문이다.
              foo = $(foo) -o
              all:;echo $(foo)
      위와 같이 Makefile 을 작성하고 터미널에서 make 를 실행시키면 다음과 같은 오류 메세지가 출력된다.
            Makefile:1: *** Recursive variable `foo' references itself (eventually).  Stop.
      이 처럼 make 는 무한 반복을 감지하면 오류를 발생시킨다.

(2) Simply expanded variable
      재귀적 확장 변수의 단점을 보완하기 위한 변수이다. := 기호를 이용해서 변수에 값을 할당한다.
      = 와는 달리 재귀적으로 작동하지 않으며, 변수에 대입된 값을 그대로 출력한다.

    ---------------------------------------------------
      $ vi Makefile
      foo := $(foo) bar
      all:;echo $(foo)

    $ make
    echo  bar
    bar
    --------------------------------------
    변수를 참조하는 시점에 저장된 값이 아무 것도 없었기 때문에 변수 대신 어떠한 값도 들어가지 않았다.
    또한, foo 변수를 스스로 참조하는 무한 반복에 빠지지 않았다. 이러한 특징 덕분에 변수 뒤에 다른 값을 추가할 수 있는 장점이 있다.
      ---------------------------------------
      $ vi Makefile
      x := hello
      x := $(x) world
      all:;echo $(x)

      $ make
      echo hello world
      hello world

■ 자동 변수(Automatic variables)
      $@ : Target 을 지칭한다.
      $< : 첫 번째 Dependency 이름을 지칭한다.
      $^ : 현재 Target이 의존하는 Dependencies 의 전체 목록 (공백으로 구분)
      $? : 현재 Target이 의존하는 대상들 중 Target 보다 새롭게 변경된 Dependencies 의 전체 목록

■ 내장 변수 (Built-in Variable)
    $(CC) : 컴파일러
    $(CFLAGS) : 컴파일 옵션

■ 리링크(Relink)
    리링크는 의존성이 변경되었을 때만 타겟을 생성하는 것을 의미한다. 즉, 의존성이 변경되지 않았다면 리링크가 수행되면 안된다.
      ------------------------------------------------
      $ vi Makefile
      bonus: $(OBJ_B)
$(AR) $(TARGET) $^
      ----------------------------------------
      OBJ_B 가 존재하면 TARGET 을 생성한다. OBJ_B 가 변경되었는지 체크하지 않기 때문에 make bonus 를 실행하면 무조건 리링크한다.

    -------------------------------------------------
      $ vi Makefile
      ifdef WITH_BONUS
  OBJ = $(OBJ_O) $(OBJ_B)
      else
  OBJ = $(OBJ_O)
      endif
        ...
      bonus:
make WITH_BONUS=1 all
      ------------------------------------------------------
      make bonus  를 하면 WITH_BONUS 라는 상수를 1로 지정하여 코드를 실행한다.
      WITH_BONUS 가 1일 때는 OBJ 를 OBJ_O  와 OBJ_B 모두 변경사항을 체크하기 때문에 리링크가 방지된다.

■ 패턴 규칙(Pattern Rules)
    % 기호는 와일드 카드와 비슷한 역할을 한다.
    -------------------------------------------------
    $ vi  Makefile
      %.o : %.c
  $(CC) $(CFLAGS) -c $<
    ------------------------------------------------------------
      오브젝트 파일(.o)는 .c 로 끝나는 파일에 의존한다는 것으로 해석할 수 있다.

    ----------------------------------------
    $ vi Makefile
      .c.o:
        $(CC) $(CFLAGS) -c $<
    -------------------------------------------
    .c.o 는 Old-fashioned 표현이라고 공식 문서에서 언급하고 있다. 기능은 동일하다.

■ 함수
      문법  :      $(function arguments)

(1) 치환 함수
    ①  subst
            $(subst ee,EE,feet on the street)
            실행 결과로 ‘fEEt on the strEEt’ 가 생성된다.

      ② 변수를 활용한 치환
                    $(변수명:pattern=replacement)
                    선언된 변수를 활용해서 문자열을 치환할 수도 있다.
            --------------------------------------------------------------------
            $ vi Makefile
            SRCS := first.c second.c third.c
            OBJS := $(SRCS:.c=.o)

            # 치환 결과
            # OBJS = first.o second.o third.o
          ---------------------------------------------------------------
          OBJS 에는 SRCS 에서 .c 와 매칭되는 문자열을 .o 로 치환한다.
          이는 patsubst 함수와 동일한 기능을 수행한다.

          ---------------------------------------------------------------
          $ vi Makefile
          SRCS := first.c second.c third.c
          OBJS := $(patsubst %.c,%.o,$(SRCS))
          # 치환 결과
          # OBJS = first.o second.o third.o
          ---------------------------------------------------------

(2) 파일 이름 관련 함수
      ①  addprefix
                          $(addprefix prefix,names...)
                          접미사를 names 앞에 추가하는 함수이다.
            예)
                  # 실행 예제
                  $(addprefix src/,foo bar)

                  # 실행 결과
                  src/foo src/bar

■ 가짜 목표 파일 .PHONY
    .PHONY 는 Target 위치에 작성하지만, 레시피 실행을 위한 이름일 뿐, 실제 파일 이름이 아니라는 것을 알려주기 위해 사용한다.
      참고로 phony 는 사전적으로 ‘가짜’라는 의미이다. 맨 윗줄에 작성하는 것이 편하다.

(1) 실제 파일 이름과 충돌 해결
      디렉토리에 파일명이 test 인 파일이 있다고 하면,
      ---------------------------------------------------------
      test :
@echo test makefile
      ---------------------------------------------------------
      실행하면 결과
      $ make test
      make: 'test' is up to date. # 출력 메시지
      -----------------------------------------------------------

      make 의 Target 과 디렉토리 내 파일명과 분리하기 위해서 .PHONY 를 사용한다.
      ------------------------------------------------
      .PHONY : test

        test :
    @echo test makefile
        -------------------------------------------------------
        실행 결과는
        $ make test
        test makefile # 출력 메시지

(2) 성능 개선
      Makefile 안에서 make 를 다시 실행시키는 경우 문제가 발생한다. 다음과 같이 디렉토리가 구성되었다고 하면,
      -------------------------------------------------------------
      /main
                |_ Makefile
                |_ /foo
                              |_ Makefile
                              |_ ... // other files
              |_ /bar
                              |_ Makefile
                              |_ ... // other files
            |_ /koo
                              |_ Makefile
                              |_ ... // other files
 
          main 디렉토리의 Makefile 이다.
          --------------------------------------------------
          $ vi Makefile
          SUBDIRS = foo bar koo
          subdirs :
      for dir in $(SUBDIRS); do \
  $(MAKE) -C $$dir; \
      done
          ----------------------------------------------------

        make subdirs 을 실행시키면 다음과 같은 문제가 생긴다.
            - 하위 make 에서 에러가 발생해도 계속 빌드가 진행된다.
            - 하나의 규칙만 실행되기 때문에 make 의 장점인 병렬 수행이 되지 않는다.
        이러한 문제를 해결하기 위해 다음과 같이 작성할 수 있다.
          -----------------------------------------------------------------------
          SUBDIRS = foo bar koo
          .PHONY : subdirs $(SUBDIRS)
          subdirs : $(SUBDIRS)

          $(SUBDIRS) :
        $(MAKE) -C $@
          ---------------------------------------------------------

          참고로 make 명령어의 -C 옵션은 makefile 을 읽기 전에 해당 디렉토리로 이동하는 옵션이다.
                subsystem:
            cd subdir && $(MAKE)
       
          위와 같은 명령어가 있을 때, 다음과 같이 작성할 수 있다.
                subsystem:
            $(MAKE) -C subdir
          병렬 수행을 하기 위해서는 make 명령어에서 -j 옵션을 사용하면 된다.