Makefile로 스크립트 관리하기
스크립트를 파일.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에서 제공하는 프로그램이기 때문에 좀 더 범용적인 느낌이 있습니다.
실제로도 사용하니 매우 편리합니다 ㅎ.ㅎ