ShellScript 와 Makefile
이번에 ShellScript 와 같이 정리해보려는 Makefile 의 경우, 보통 C 로 작성된 소스를 의존성을 기술하여 make 라는 명령어로 컴파일을 도와주는 스크립트다.
요즘 go 에서 makefile 을 활용하는 몇가지 글들을 접하면서 ShellScript 보다 체계적이고 깔끔하게 느껴져서 이제는 대부분을 makefile 로 자동화 작업을 하고 있다.
이 글에서는 ShellScript 와 Makefile 어느 것이 더 좋다는 의견 보다는 개발에 사용하는 자동화 템플릿을 만들어 보았을 때, 그 구현이 어떻게 다른지 정도를 정리해본다.
자동화 스크립트에 어떤 것을 기대하나
내가 자동화 스크립트에 기대하는 바나 필요한 기능들을 적어보자.
1. 자동화 스크립트 도움말 표시
: 어플리케이션 사용자에게 가이드 역할을 해주며, 특정 어플리케아션 명령어나 스크립트의 기본이라 생각한다.
2. Docker 컨테이너의 시작/종료
: 내게 개발을 위해 꼭 필요한 도구이며 단순히 생성/시작/종료면 된다.
3. 어플리케이션의 시작/종료/프로세스 확인
: 이 부분은 디버깅을 위한 부분이라 단순히 수동 시작, 종료, 프로세스 상태 확인 정도면 만족한다.
4. 어플리케이션 컴파일
: 컴파일이 필요한 언어의 경우, 배포를 위한 빌드기능이 있어야 한다.
5. APIDOC, 개발문서 생성
: 언어를 막론하고 APIDOC 자체가 내게는 꼭 필요한 솔루션이고 새로운 버전을 빌드할때 개발 문서가 같이 갱신 되었으면 한다.
6. 기타
: 특정 언어나 각자 개발 스타일에 따라 필요한 것들. 의존성 관리나 TDD 에 필요한 것들.
자동화 스크립트 템플릿 작성하기
처음 프로그램을 접하는 사람에게 혹은 해묵은 프로젝트를 다시 들여다 볼 때, 어떻게 사용해야 할지 알려주는 도움말 역할을 해준다. 이를테면 아래와 같은 모습의 도움말.
ShellScript 와 Makefile 로 이러한 도움말을 표시하기 위한 소스 자체가 하나의 ::자동화 템플릿::이 아닐까 싶다. 완성된 도움말에 간단히 각자 원하는 기능을 넣으면 그만이니까.
간단히 Makefile 과 ShellScript 로 아래와 같은 결과물을 내는 스크립트를 작성했다. 뼈대를 만들고 하나 하나 살을 붙여보자.
Makefile 템플릿 구현
템플릿 도움말 구현
PRJ_NAME=$(shell basename "$(PWD)")
PRJ_DESC=<$(PRJ_NAME)>는, 자동화를 위한 템플릿을 만들어보는 프로젝트입니다.\\n 만들어 놓으면 언젠가 또 쓰겠지.
PRJ_BASE=$(shell pwd)
.DEFAULT: help
.SILENT:;
##help: 도움말 (기본명령)
help: Makefile
echo ""
echo " $(PRJ_DESC)"
echo ""
echo " 사용 방법:"
echo ""
echo " make 실행명령"
echo ""
echo " 실행 명령:"
echo ""
sed -n 's/^##/ /p' $< | column -t -s ':' | sed -e 's/^/ /'
echo ""
##all: 모든 실행파일을 빌드하며, 새로운 개발문서를 작성.
all:
# 빌드 명령
echo " > Compile Done."
간단히 설명을 붙이자면, 위 소스에서 help: all
부분이 명령어 선언이고, 그 이하가 명령어 묶음 정도로 보면 된다. (함수명과 함수 본문으로 봐도 좋다.) help:
오른쪽에 위치한 Makefile 은 종속성을 의미하며 Makefile 이 있어야 help
라는 명령을 실행 할 수 있다는 정도로 보면 된다. 자세한 Makefile 가이드는 아래 링크에 모아 두었으니 참고하면 된다.
도움말을 만들기 위해 만들어진 또 하나의 규칙중 하나가 ##help: 도움말
이 부분인데, help 소스의 sed 명령행
을 보면 파일내 ##
주석을 스페이스로 치환하고 컬럼으로 열을 맞추는 부분이 있는데, 이 부분이 위에 이미지로 소개했던 결과물 이미지처럼, 스크립트 내 ##
로 시작하는 주석을 이쁘게 명령어 도움말로 정리해 주는 부분이다.
새로운 명령어(함수)의 추가는 다음과 같이 도커 사용에 대한 예시를 추가 해두었다.
# 간단히 도커 컨테이너에 관련된 디렉토리와 파일를 변수로 묶었다
DOCKER_BASE=$(PRJ_BASE)/build/docker
DOCKER_CFG=$(DOCKER_BASE)/docker-compose.yaml
## d-create: 새로운 도커 컨테이너를 만듭니다.
d-create: $(DOCKER_CFG)
cd $(DOCKER_BASE); docker-compose create; cd ${PRJ_BASE}
## d-start: 도커 컨테이너를 시작합니다.
d-start: $(DOCKER_CFG)
cd $(DOCKER_BASE); docker-compose start; cd ${PRJ_BASE}
## d-stop: 도커 컨테이너를 정지합니다.
d-stop: $(DOCKER_CFG)
cd $(DOCKER_BASE); docker-compose stop; cd ${PRJ_BASE}
기능 추가를 위한 간단한 Makefile 문법
- 변수 반복
NUMBERS=srv.service.js srv.chat.js
loopvar:
$(foreach number, $(NUMBERS), echo ">> start" $(number) &)
* 명령어 반복
loopcmd: apple banana orange
apple:
echo "apple cmd"
banana:
echo "banana cmd"
orange:
echo "orange cmd"
ShellScript 템플릿 구현
템플릿 도움말 구현
#!/bin/bash
PRJ_NAME=$(basename "$(PWD)")
PRJ_DESC="<${PRJ_NAME}>는, 자동화를 위한 템플릿을 만들어보는 프로젝트입니다.\n 만들어 놓으면 언젠가 또 쓰겠지."
PRJ_BASE=$(pwd)
_CMD=$1;
case "$_CMD" in
##help: 도움말 (기본명령)
"help")
echo ""
echo -e " ${PRJ_DESC}"
echo ""
echo " 사용 방법:"
echo ""
echo " make 실행명령"
echo ""
echo " 실행 명령:"
echo ""
sed -n 's/^##/ /p' $0 | column -t -s ':' | sed -e 's/^/ /'
echo ""
;;
##all: 모든 실행파일을 빌드하며, 새로운 개발문서를 작성.
"all")
# 빌드 명령
echo " > Compile Done."
;;
"d-stop")
cd $(DOCKER_BASE); docker-compose stop; cd ${PRJ_BASE}
;;
"d-start")
cd $(DOCKER_BASE); docker-compose start; cd ${PRJ_BASE}
;;
*)
$0 help
exit
esac
ShellScript 도 크게 다르지 않다. 템플릿 구조상의 차이점은 Makefile 과 다른 점은 case 문으로 명령어를 분기했다는 것이고, Makefile 보다는 더 익숙한 편이지만, 다양한 기능을 가진 자동화 스크립트를 만들기에는 좀 장황해 질 수 있겠다.
기능 추가를 위한 간단한 ShellScript 문법
- 변수 반복
'loopvar')
_PROCESS_LIST=("srv.service.js" "srv.chat.js" "srv.cron.js" "srv.gate.js" "srv.api.js")
for name in ${_PROCESS_LIST[@]}
{
echo ">> start $name";
}
;;
* 명령어 반복
'loopcmd')
$0 apple;
$0 banana;
$0 orange;
;;
'apple')
echo 'apple';
;;
'banana')
echo 'banana';
;;
'orange')
echo 'orange';
;;
Notes for new Make users
make와 Makefile - 멍멍멍
코코넛냠냠 :: Makefile 문법 요약
Golang: Don’t afraid of makefiles - Radomir Sohlich