ちょっと話題の記事

Makefile로 스크립트 관리하기

Makefile로 스크립트를 관리해봅니다. 있을법한 상황을 예제로 만들어 적용해봅시다.
2023.04.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

스크립트를 파일.sh 형태의 스크립트로 관리해도 되긴합니다. 그런데 스크립트의 쉘 환경을 정하고 싶다던지 스크립트간에 의존성이 있다던지한 경우에는 Make가 편합니다. Make는 GNU 소프트웨어 중 하나로, Make에 사용하는 파일이 Makefile입니다.

https://www.gnu.org/software/make/

간단한 예제로 살짝 맛만 봅시다.

에제

SHELL = /bin/bash

compose-start:
    docker compose build && docker compose up

docker-build: env-$${ENV}
    . .env.$${ENV}; \
    docker build -t app . \
        --build-arg SERVER_HOST=$${SERVER_HOST} \
        --build-arg DB_PASSWORD=$${DB_PASSWORD} 

ecr-push: env-$${ENV}
    . .env.$${ENV}; \
    ENV=$${ENV} make docker-build; \
    docker tag app:latest $${AWS_ACCOUNT_ID}.dkr.ecr.$${AWS_REGION}.amazonaws.com/app:latest; \
    docker push $${AWS_ACCOUNT_ID}.dkr.ecr.$${AWS_REGION}.amazonaws.com/app:latest

env-%:
    @if [ ! -f .env.$* ]; then \
        echo "Environment .env.$* file not found"; \
        exit 1; \
    fi

.PHONY: \
        compose-start \
        docker-build \ 
        ecr-push \
        env-%

하나하나 봐봅시다. 순서는 실제 보기좋은 순이 아니라 이해하기 쉬운 순으로 두었습니다.

SHELL = /bin/bash

처음으로 Make가 동작할 쉘을 지정할 수 있습니다.

compose-start:
    docker compose build && docker compose up

두 개의 커맨드를 엮었네요. 사용은 make compose-start로 사용하게 됩니다. 노드에서의 npm run compose-start 같은 느낌입니다.

이 커맨드의 묶음 단위를 타겟이라하고 타겟들은 아래와 같은 구성을 가집니다.

target: dependencies
  commands

첫 타겟은 디펜던시가 없었지만, 다음에는 디펜던시가 있는 타겟을 봐봅시다.

docker-build: env-$${ENV}
    . .env.$${ENV}; \
    docker build -t app . \
        --build-arg SERVER_HOST=$${SERVER_HOST} \
        --build-arg DB_PASSWORD=$${DB_PASSWORD}

디펜던시가 env-$${ENV}가 있네요. 디펜던시가 있다면 타겟을 실행하기전에 디펜던시 먼저 실행하게 됩니다. 또 바로 안 와닿는게 $${ENV} 이 부분일텐데요. 변수를 지정하는 부분입니다. 만약 Make 안에서 지정한 변수를 사용한다면 ${ENV}로 가능하지만, 환경변수에 있는 값을 사용하고 싶다면 $${ENV} 같이 $$ 달러사인 두개를 사용해야합니다. $${ENV}${ENV}로 평가되어서 쉘 상에서 환경변수를 얻을 수 있기 때문입니다.

만약 ENV=dev make docker-build로 커맨드를 넣는다면 $${ENV}에서는 달러사인 하나로 가능하지만, 만약 Make를 실행하면서 넣어주지 않는 환경변수라면 달러사인 두개로 사용해야 환경변수에서 제대로 가져오게됩니다. 위의 예시에서는 .env.dev 같이 환경변수 파일에서 환경변수를 넣어주기 도커를 빌드할 때에는 달러사인 두개로 해야합니다.

ecr-push: env-$${ENV}
	. .env.$${ENV}; \
	ENV=$${ENV} make docker-build; \
	docker tag app:latest $${AWS_ACCOUNT_ID}.dkr.ecr.$${AWS_REGION}.amazonaws.com/app:latest; \
	docker push $${AWS_ACCOUNT_ID}.dkr.ecr.$${AWS_REGION}.amazonaws.com/app:latest

디펜던시는 똑같이 있지만 타겟에서 다른 타겟을 부르는 경우입니다. 타겟을 사용하듯이 내부에서도 사용하면됩니다.

env-%:
    @if [ ! -f .env.$* ]; then \
        echo "Environment .env.$* file not found"; \
        exit 1; \
    fi

마지막으로 디펜던시로 존재했던 타겟입니다. env-%에서 %는 타겟을 부를때 설정되게 됩니다. 예를 들어 make env-dev 이면 $*dev가 되게 됩니다.

또한, if 문이 가능합니다. @if [ ! -f .env.$* ];.env.${ENV}이 없으면 조건에 걸리게 됩니다. 구체적으로 -f로 파일이 있는지 확인하는데 !로 반대조건이 되기 때문에 파일이 없는 경우에 조건에 걸리게 됩니다.

@if문의 문법이 아니라, Make에서 타겟을 실행하면 해당 커맨드를 출력해주는데 이 출력을 생략해줍니다. 어차피 위 타겟은 파일이 없는 경우에만 출력해도 되기 때문에 생략해주기 위해 사용합니다. 또한, CI/CD 서버같은 곳에서 노출되면 안되는 값들을 숨기는 용도로도 사용할 수 있습니다.

따라서 env-%를 디펜던시로 가진 타겟들은 실행되기전에 env.${ENV} 파일이 있는지 확인이 되고 없으면 에러 메시지와 함께 종료되게 됩니다. 사실 없어도 에러가 나서 실패하겠지만 있는게 좀 더 친절하겠죠?

.PHONY: \
        compose-start \
        docker-build \ 
        ecr-push \
        env-%

마지막으로 포니입니다. 기본적으로 타겟명과 파일명의 겹치는 경우에도 동작하게 해줍니다. 이외에도 하위 경로에도 Make가 동작해야하는 경우 실패하면 전부 중지시킨다던지 추가기능이 있습니다.

https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html

마무리

원래는 C언어 같은 언어를 빌드하기 위해 만들어진 프로그램이지만 매우 유용해서 일반적인 스크립트를 관리하기에도 용이합니다. 물론 좀 더 발전된 여러 라이브러리들이 있지만, Make는 GNU에서 제공하는 프로그램이기 때문에 좀 더 범용적인 느낌이 있습니다.

실제로도 사용하니 매우 편리합니다 ㅎ.ㅎ