minishell과 readline

sham·2021년 12월 17일
0

개요

minishell 과제가 개편되면서 사용가능 함수 중 readline 디렉토리의 함수들을 이용하면 프롬프트를 쉽게 구현할 수 있다고 한다.
정말 편리하기는 했지만 자료가 많지 않아 막히는 부분이 있어 자료를 찾을 때면 난항을 겪기 일쑤였다.
이 하찮은 포스팅이 삽질을 조금이나마 줄일 수 있다면 정말 좋겠다.

<readline/readline.h>, <readline/history.h>

https://tiswww.case.edu/php/chet/readline/rltop.html
https://tiswww.case.edu/php/chet/readline/readline.html

헤더의 이름처럼 두 라이브러리 모두 readline 디렉토리에 속한다. Mac OS X에서는 해당 readline이라는 디렉토리가 이미 존재하기는 하지만, rl_replace_line 같은 함수들을 이용해야만 프롬프트를 완전하게 구현할 수가 있게 되기에 GNU Library 라이브러리에 존재하는 라이브러리를 사용해야만 한다.

함수 설명

readline

char *readline(const char str*);

str를 출력하고 프롬프트를 열어서 표준입력으로 문자열을 입력받는다. 개행(엔터)를 받으면 지금까지 입력된 문자열을 리턴한다. 문자열을 입력받기 전까지는 다음 코드로 진행되지 않는다. rl_replace_line, rl_redisplay 등으로 프롬프트가 비워져도 계속 문자열을 받는 상태가 된다.

rl_replace_line

void rl_replace_line(const char str*, int);

GNU 라이브러리에만 들어있는 함수이다.

현재까지 입력된 프롬프트의 문자열을 str로 바꿔준다. int는 길이도, 디스크립터로 아니라서 대체 뭔지 모르겠다.

ctrl + C 처럼 프롬프트를 입력하지는 않고 새로운 프롬프트를 출력해야 할 때 rl_replace_line(””, 1);처리를 해준다면 새로운 프롬프트를 비워줄 수 있게 된다.

rl_on_new_line

int rl_on_new_line(void);

rl_redisplay를 실행하기 위해 필요한 함수이다.

rl_redisplay

void rl_redisplay(void);

rl_replace_line를 출력하지 않으면 작동하지 않는다. readline 함수의 인자로 넣은 문자열을 다시 출력한다.

add_history

int add_history(const char *);

인자에 넣은 문자열을 history로 저장한다.

프롬프트가 열린 상태에서 키보드 방향키 위 아래를 통해 이제껏 프롬프트에 입력한 문자열을 불러올 수 있다. 스택처럼 가장 마지막에 넣은 문자열부터 불러온다.

readline을 이용해서 프롬프트 뼈대 구현

#include <signal.h>
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <stdlib.h>

void handler(int signum)
{
    if (signum != SIGINT)
        return;
    printf("ctrl + c\n");
    rl_on_new_line();
    rl_replace_line("", 1);
    rl_redisplay();
}

int main(void)
{
    int ret;
    char *line;

    signal(SIGINT, handler);
    while (1)
    {
        line = readline("input> ");
        if (line)
        {
            if (ret)
                printf("output> %s\n", line);
            add_history(line);
            free(line);
            line = NULL;
        }
        else
        {
            printf("ctrl + d\n");
        }
    }
    return (0);
}

while 문을 돌면서 readline을 통해 표준입력으로 입력한 문자열을 line에 저장한다.

엔터를 입력하면 line에 문자열이 동적할당되어 저장된다. 적절한 파싱 후 명령어 처리를 진행하면 될 것이다.

프롬프트가 비었을 때 ctrl + D를 입력하면 EOF가 전송된다. bash처럼 exit을 출력하고 종료되게 처리를 하면 될 것이다.

signal을 설정해서 도중에 ctrl + C가 입력되면 지금까지 입력된 프롬프트를 유지한 채 개행한 후 텅 빈 프롬프트를 받게끔 하자. printf로 출력하는 문자열은 기존의 input> 문자열 에 덮어 씌우고 개행된다.

터미널 제어

https://80000coding.oopy.io/13bd7bb7-3a7f-4b51-b84a-905c47368277

#include "src/minishell.h"

void sig_handler(int signal)
{
    printf("signal : %d\n", signal);
    if (signal == SIGINT)
    {
        printf("\nnnoshell$");
    }
    else if (signal == SIGTERM)
    {
        printf(" exit\n");
        exit(-1);    }
    else
    {
    }
}

void setting_signal()
{
    signal(SIGINT, sig_handler);  // CTRL + C
    signal(SIGTERM, sig_handler); // CTRL + D
    signal(SIGQUIT, sig_handler); // CTRL + /
}

int main(int argc, char **argv, char **envp)
{
    char *str;
    // 환경변수
    setting_signal();
    while (1)
    {
        str = readline("nanoshell$ ");
        add_history(str);
        printf("str : %s\n", str);
        free(str);
    }
    /* 함수종료 */
    return (0);
}

ctrl + D에 해당하는 시그널인 SIGTERM를 signal 함수로 잡아보려고 해도 인식하지 못하는 상황이 발생했다. 이를 해결하기 위해서는 noncanonical input mode를 사용하면 된다고 한다.

canonical, noncanonical

canonical input mode(표준 입력 모드)는 우리가 프로그램에서 흔히 사용하는 모델이다. 입력이 들어오면 개행 혹은 EOF와 같은 종료 문자가 나올때까지 처리를 기다리고 있다가 한번에 문장 한 줄을 처리한다.

noncanonical input mode(비표준 입력 모드)는 문자가 행으로 그룹화 되지 않으며, ERASE 및 KILL 처리가 수행되지 않는다. 문자를 읽자마자 바로 처리하는 방식으로 동작하기 때문이다.

c_lflag의 속성

#include <termios.h>

// termios 구조체가 가지고 있는 property
struct termios
{
    tcflag_t c_iflag;    /* input flags */
    tcflag_t c_oflag;    /* output flags */
    tcflag_t c_cflag;    /* control flags */
    tcflag_t c_lflag;    /* local flags */
    cc_t     c_cc[NCCS]; /* control chars */
    speed_t  c_ispeed;   /* input speed */
    speed_t  c_ospeed;   /* output speed */
};

tcgetattr(STDIN_FILENO, &term);
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 1; // 1 바이트씩 처리
term.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &term);

터미널를 제어할 수 있는 구조체 termiosc_lflag를 통해 특수문자를 제어할 수 있다.

https://studyc.tistory.com/5
&는 비트 AND 연산, ~는 비트 NOT 연산 , |은 비트 OR 연산이다. 본래의 속성이 11111111 형태의 비트로 저장되어 있다고 했을 때, 각각의 속성에 할당된 자리의 비트가 있을 것이고, 위의 속성들을 OR 연산한 비트를 NOT 연산하게 되면 해당 속성들은 0으로 전환되어 AND 연산이 들어가게 되는 것이다.

tcsetattr의 옵션으로 들어가는 optional_actions에 TCSNOW는 환경 변수 0으로 등록되어 있는데, 속성을 바로 변경한다는 것을 의미한다.

위와 같은 방법으로 진행하면 될 것 같았으나...

readline은 다르다

readline에서는 위의 링크대로 진행하면 과제에서 요구한 시그널 처리가 안되는 것은 기본이고 히스토리를 보기 위해 키보드 방향키를 조작을 해도 ^[[A, ^[[B 라는 동작으로 인식이 되는 현상이 발생한다. 다른 방법을 사용해야만 하는 것인데, 어찌저찌 삽질을 해서 해결하기는 했다.

#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <termios.h>

// gcc testsignal.c -lreadline -L/Users/sham/.brew/opt/readline/lib -I/Users/sham/.brew/opt/readline/include

void sig_handler(int signal)
{
    if (signal == SIGINT)
    {
        //printf("\033[K"); 지워버리는 것을 원하지는 않았음.
        printf("nanoshell$ \n");
    }

    if (rl_on_new_line() == -1) // readline으로 설정한 문자열을 한 번 출력한다?
        exit(1);
    rl_replace_line("", 1); // 프롬프트에 이미 친 문자열을 싹 날려준다.
    rl_redisplay();         // 프롬프트 커서가 움직이지 않게 해준다.
}

void setting_signal()
{
    signal(SIGINT, sig_handler); // CTRL + C
    signal(SIGQUIT, SIG_IGN);    // CTRL + /
                                 // signal(SIGTERM, sig_handler);
}

int main(int argc, char **argv, char **envp)
{
    char *str;
    struct termios term;
    tcgetattr(STDIN_FILENO, &term);
    term.c_lflag &= ~(ECHOCTL);
    tcsetattr(STDIN_FILENO, TCSANOW, &term);
    setting_signal();

    while (1)
    {
        str = readline("nanoshell$ ");
        if (!str)
        {
            printf("\033[1A");
            printf("\033[10C");
            printf(" exit\n");
            exit(-1);
        }
        else if (*str == '\0')
        {
            free(str);
        }
        else
        {
            add_history(str);
            printf("%s\n", str);
            free(str);
        }
    }
    /* 함수종료 */
    return (0);
}

Ctrl + C, Ctrl + \ 시 나오는 ^C, ^\

#include <termios.h>

int main(void)
{
	struct termios term;
    tcgetattr(STDIN_FILENO, &term);
    term.c_lflag &= ~(ECHOCTL);
    tcsetattr(STDIN_FILENO, TCSANOW, &term);
    setting_signal();
}

위의 링크에서는 명령을 표준입력으로 들어오기 때문에 new_term.c_lflag &= ~(ICANON | ECHO); 로 정규 모드와 반향을 꺼주었지만 readline은 제어문자와 관련된 ECHOCTL 속성만 꺼주면 해결이 된다. c_cc 속성값을 건들일 필요 없이 해당 c_lflag만 설정해주어도 시그널을 보내면 제어문자의 반향이 출력되지 않는다.

Ctrl + D 시 개행 방지

   if (!str)
        {
            printf("\033[1A"); // 커서를 위로 한 줄 올린다.
            printf("\033[10C"); // 커서를 10만큼 앞으로 전진시킨다.
            printf(" exit\n");
            exit(-1);
        }

https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html

Ctrl + D는 시그널이 아닌 EOF로 들어가서 str에 NULL이 들어가고, 이를 조건문에서 체크해서 제어할 수 있다. 그런데 bash 처럼 exit을 출력하려고 하자 minishell$ 이 한 번 출력되어 버리는 현상이 발생했다. 이를 해결하는 방법은 의외로 간단했는데, ANSI 이스케이프 시퀀스로 커서를 제어해서 해결해주었다.

개행 이전 줄로 이동하고 적절한 위치에 커서를 배치하고 exit을 출력해주면 bash와 동일하게 출력이 되는 것을 확인했다.

클러스터 맥 기준 readline 설치 및 사용

testsignal.c:24:5: error: implicit declaration of function 'rl_replace_line' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    rl_replace_line("", 1);

기존처럼 -lreadline으로 컴파일하게 되면 rl_replace_line을 쓸 수 없다고 에러를 뱉는다.

이는 <readline/readline.h> <readline/history.h> 라이브러리가 두 버전 존재하기 때문인데, -lreadline 플래그를 쓰면 Mac OS X 버전을 사용하게 된다. 이 Unix 계열 버전은 기본적인 기능만 제공하기에 추가적인 기능을 이용하려면 GUL Library의 readline을 사용해야 한다.

GUL Library의 readline 설치

GNU 라이브러리를 사용하려면 brew로 readline 라이브러리를 설치해주어야 한다.

brew install readline 로 라이브러리를 설치하고 brew info readline 를 터미널에 입력하면 readline 라이브러리가 어디에 위치하여 있는지 상세히 나와있다.

필자 기준 라이브러리는 Users/sham/.brew/opt/readline/lib에, 헤더 파일은 Users/sham/.brew/opt/readline/include 에 들어있었다.

Makefile에서의 설정

위의 경로를 그대로 Makefile에 하드코딩하고 본인의 맥에서만 개발을 진행하고 평가까지 한다면 참 좋겠지만, 다른 맥에서 프로그램을 실행한다면 제대로 경로를 찾지 못하게 된다.

우리가 -l를 이용해 라이브러리를 사용하게 되면 리눅스 기준 /lib, /usr/lib, /usr/local/lib에서 라이브러리를 찾게 되고, #include <>로 표준라이브러리 헤더를 사용하게 되면 /usr/include 에서 헤더를 찾게 된다.

# **************************************************************************** #
#                                                                              #
#                                                         :::      ::::::::    #
#    Makefile                                           :+:      :+:    :+:    #
#                                                     +:+ +:+         +:+      #
#    By: sham <sham@student.42.fr>                  +#+  +:+       +#+         #
#                                                 +#+#+#+#+#+   +#+            #
#    Created: 2021/06/01 16:48:56 by sham              #+#    #+#              #
#    Updated: 2021/12/17 14:51:48 by sham             ###   ########.fr        #
#                                                                              #
# **************************************************************************** #

NAME = nanoshell
CC = gcc
CFLAGS = -Werror -Wall -Wextra 
LINKING_FLAGS = -lreadline -L${HOME}/.brew/opt/readline/lib
COMFILE_FLAGS = -I${HOME}/.brew/opt/readline/include
MAIN_SRCS =  $(addprefix src/, main.c)
INSTALL_SRCS = $(addprefix src/install/, execve.c fork.c handle_dis.c)
PARSE_SRCS = $(addprefix src/parse/, ft_cmd_list.c ft_list.c ft_env_list.c ft_parser.c ft_string_list.c ft_check_type.c ft_env_parser.c)
LIBFT_SRCS = $(addprefix src/libft/, ft_strdup.c ft_strjoin.c ft_strlen.c ft_strncmp.c ft_cmpstr.c ft_split.c ft_strlcpy.c )
BUILT_IN_SRCS = $(addprefix src/built_in/, ft_cd.c ft_pwd.c ft_echo.c ft_exit.c)
ERROR_SRCS = $(addprefix src/error/, ft_error.c )

MAIN_OBJS = $(MAIN_SRCS:.c=.o)
INSTALL_OBJS = $(INSTALL_SRCS:.c=.o)
PARSE_OBJS = $(PARSE_SRCS:.c=.o)
LIBFT_OBJS = $(LIBFT_SRCS:.c=.o)
BUILT_IN_OBJS = $(BUILT_IN_SRCS:.c=.o) 
ERROR_OBJS = $(ERROR_SRCS:.c=.o) 

all : $(NAME)

$(NAME) : $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS) $(INSTALL_OBJS) $(BUILT_IN_OBJS) $(ERROR_OBJS)
	$(CC) $(CFLAGS) $(LINKING_FLAGS) $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS) $(INSTALL_OBJS) $(BUILT_IN_OBJS) $(ERROR_OBJS) -o $(NAME) 

%.o: %.c
	$(CC) $(CFLAGS) $(COMFILE_FLAGS) -c $< -o $@

clean :
	rm -rf $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)

fclean : clean
	rm -rf $(NAME)

re : fclean all

.PHONY : all clean fclean re

${HOME} 환경변수를 이용해서 경로 부분이 자동으로 치환되게끔 하자. brew로 readline을 설치한 맥이라면 어디서든 경로를 잘 찾을 수 있을 것이다.

Makefile 컴파일 이슈

  • 이슈 내용

    implicit declaration of function 'rl_replace_line' is invalid in C99

    컴파일 옵션
    http://egloos.zum.com/ssulsamo/v/5247105

    gcc testsignal.c -lreadline -L/Users/sham/.brew/opt/readline/lib -I/Users/sham/.brew/opt/readline/include 로 컴파일이 되는 것을 확인하고 makefile로 관리하려고 하는데 갑자기 이런 오류를 맞닥뜨렸다. 컴파일 옵션에서 문제가 생길 줄이야... 지금 버전인 C99에서는 해당 함수를 쓸 수 없는 모양이었다.
    CFLAGS = -Werror -Wall -Wextra -lreadline -L /usr/local/opt/readline/lib -I /usr/local/opt/readline/include

    ??? 왜 메이크 파일만???

    # **************************************************************************** #
    #                                                                              #
    #                                                         :::      ::::::::    #
    #    Makefile                                           :+:      :+:    :+:    #
    #                                                     +:+ +:+         +:+      #
    #    By: sham <sham@student.42.fr>                  +#+  +:+       +#+         #
    #                                                 +#+#+#+#+#+   +#+            #
    #    Created: 2021/06/01 16:48:56 by sham              #+#    #+#              #
    #    Updated: 2021/12/08 21:11:46 by sham             ###   ########.fr        #
    #                                                                              #
    # **************************************************************************** #
    
    NAME = nanoshell
    CC = gcc 
    # CFLAGS = -Werror -Wall -Wextra -lreadline -L .brew/opt/readline/lib -I .brew/opt/readline/include
    READLINE = -Werror -Wall -Wextra -lreadline -L/Users/sham/.brew/opt/readline/lib -I/Users/sham/.brew/opt/readline/include
    MAIN_SRCS = $(addprefix src/, temp.c)
    SIGNAL_SRCS = testsignal.c
    OBJS = $(MAIN_SRCS:.c=.o)
    SIGNAL_OBJS = $(SIGNAL_SRCS:.c=.o)
    .PHONY : all clean fclean re
    
    all : $(NAME)
    
    signal : $(SIGNAL_OBJS)
    	$(CC) $(READLINE) $(SIGNAL_OBJS) -o signal
    
    $(NAME) : $(MAIN_SRCS)
    	$(CC) $(OBJS) -o $(NAME) 
    
    clean :
    	rm -rf $(OBJS) 
    
    fclean : clean
    	rm -rf $(NAME)
    
    re : fclean all
    gcc -Werror -Wall -Wextra -lreadline -L/Users/sham/.brew/opt/readline/lib -I/Users/sham/.brew/opt/readline/include testsignal.c
    make 파일만 사용하니 에러가 난다??? makefile로 커맨드를 실행하면 왜 인식을 못하지?
    make signal
    > gcc     -c -o testsignal.o testsignal.c
    -c -o가 왜 붙고, $(READLINE)으로 넣은 플래그는 인식을 못하고... CFLAGS로 넣어주면 -c -o가 튀어나오고...

컴파일, 링킹에 대한 이해 부족과 makefile에 대해 잘못 알고 있어서 생긴 이슈였다.

  • 이슈 해결 과정
    # **************************************************************************** #
    #                                                                              #
    #                                                         :::      ::::::::    #
    #    Makefile                                           :+:      :+:    :+:    #
    #                                                     +:+ +:+         +:+      #
    #    By: sham <sham@student.42.fr>                  +#+  +:+       +#+         #
    #                                                 +#+#+#+#+#+   +#+            #
    #    Created: 2021/06/01 16:48:56 by sham              #+#    #+#              #
    #    Updated: 2021/12/13 11:50:01 by sham             ###   ########.fr        #
    #                                                                              #
    # **************************************************************************** #
    
    NAME = nanoshell
    CC = gcc
    CFLAGS = -Werror -Wall -Wextra 
    READLINE_FLAGS = -lreadline -L${HOME}/.brew/opt/readline/lib -I${HOME}/.brew/opt/readline/include
    MAIN_SRCS =  $(addprefix src/, main.c) src/install/execve.c src/install/fork.c
    PARSE_SRCS = $(addprefix src/parse/, ft_cmd_list.c ft_list.c ft_env_list.c ft_parser.c ft_string_list.c ft_check_type.c ft_env_parser.c)
    LIBFT_SRCS = $(addprefix src/libft/, ft_strdup.c ft_strjoin.c ft_strlen.c ft_strncmp.c ft_cmpstr.c ft_split.c ft_strlcpy.c)
    
    MAIN_OBJS = $(MAIN_SRCS:.c=.o)
    PARSE_OBJS = $(PARSE_SRCS:.c=.o)
    LIBFT_OBJS = $(LIBFT_SRCS:.c=.o)
    
    .PHONY : all clean fclean re
    
    all : $(NAME)
    
    $(NAME) : $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)
    	$(CC) $(CFLAGS) $(READLINE_FLAGS) $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS) -o $(NAME) 
    
    clean :
    	rm -rf $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)
    
    fclean : clean
    	rm -rf $(NAME)
    
    re : fclean all
    Makefile을 이용해 컴파일 할 때 위처럼 컴파일 옵션이 인식되지 않는 이슈가 발생했는데, 목적 파일을 찾아서 자동으로 컴파일 할 때의 경우를 지정해주지 않았기 때문에 기본으로 설정된 gcc -Werror -Wextra -Wall 플래그만 적용한 채로 컴파일했기 때문이다.
    # **************************************************************************** #
    #                                                                              #
    #                                                         :::      ::::::::    #
    #    Makefile                                           :+:      :+:    :+:    #
    #                                                     +:+ +:+         +:+      #
    #    By: sham <sham@student.42.fr>                  +#+  +:+       +#+         #
    #                                                 +#+#+#+#+#+   +#+            #
    #    Created: 2021/06/01 16:48:56 by sham              #+#    #+#              #
    #    Updated: 2021/12/13 13:18:18 by sham             ###   ########.fr        #
    #                                                                              #
    # **************************************************************************** #
    
    NAME = nanoshell
    CC = gcc
    CFLAGS = -Werror -Wall -Wextra -lreadline -L${HOME}/.brew/opt/readline/lib -I${HOME}/.brew/opt/readline/include
    MAIN_SRCS =  $(addprefix src/, main.c) src/install/execve.c src/install/fork.c
    PARSE_SRCS = $(addprefix src/parse/, ft_cmd_list.c ft_list.c ft_env_list.c ft_parser.c ft_string_list.c ft_check_type.c ft_env_parser.c)
    LIBFT_SRCS = $(addprefix src/libft/, ft_strdup.c ft_strjoin.c ft_strlen.c ft_strncmp.c ft_cmpstr.c ft_split.c ft_strlcpy.c)
    
    MAIN_OBJS = $(MAIN_SRCS:.c=.o)
    PARSE_OBJS = $(PARSE_SRCS:.c=.o)
    LIBFT_OBJS = $(LIBFT_SRCS:.c=.o)
    
    all : $(NAME)
    
    $(NAME) : $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)
    	$(CC) $(CFLAGS) $(COMFILE_FLAGS) $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS) -o $(NAME) 
    
    clean :
    	rm -rf $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)
    
    fclean : clean
    	rm -rf $(NAME)
    
    re : fclean all
    
    .PHONY : all clean fclean re
    처음에는 CFLAGS의 값을 수정해버려서 목적 파일로 컴파일 할 때 설정된 값을 플래그, 옵션이 포함된 값으로 바꿔주기로 했는데, 사용되지 않는 linker가 들어갔다며 에러를 뱉었다. 소스파일을 목적파일로 컴파일 할 때 라이브러리를 지정해주면 에러를 뱉는다. 그도 그럴것이, 라이브러리는 목적 파일과 같이 링킹을 해서 실행 파일을 만드는데 그 목적 파일을 컴파일하면서 사용하지도 않을 라이브러리를 지정해주었으니 에러를 뱉어버린 것이다.
    # **************************************************************************** #
    #                                                                              #
    #                                                         :::      ::::::::    #
    #    Makefile                                           :+:      :+:    :+:    #
    #                                                     +:+ +:+         +:+      #
    #    By: sham <sham@student.42.fr>                  +#+  +:+       +#+         #
    #                                                 +#+#+#+#+#+   +#+            #
    #    Created: 2021/06/01 16:48:56 by sham              #+#    #+#              #
    #    Updated: 2021/12/13 12:56:13 by sham             ###   ########.fr        #
    #                                                                              #
    # **************************************************************************** #
    
    NAME = nanoshell
    CC = gcc
    CFLAGS = -Werror -Wall -Wextra 
    COMFILE_FLAGS = -lreadline -L${HOME}/.brew/opt/readline/lib
    OBJ_FLAGS = -I${HOME}/.brew/opt/readline/include
    MAIN_SRCS =  $(addprefix src/, main.c) src/install/execve.c src/install/fork.c
    PARSE_SRCS = $(addprefix src/parse/, ft_cmd_list.c ft_list.c ft_env_list.c ft_parser.c ft_string_list.c ft_check_type.c ft_env_parser.c)
    LIBFT_SRCS = $(addprefix src/libft/, ft_strdup.c ft_strjoin.c ft_strlen.c ft_strncmp.c ft_cmpstr.c ft_split.c ft_strlcpy.c)
    
    MAIN_OBJS = $(MAIN_SRCS:.c=.o)
    PARSE_OBJS = $(PARSE_SRCS:.c=.o)
    LIBFT_OBJS = $(LIBFT_SRCS:.c=.o)
    
    all : $(NAME)
    
    $(NAME) : $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)
    	$(CC) $(CFLAGS) $(COMFILE_FLAGS) $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS) -o $(NAME) 
    
    %.o: %.c
    	$(CC) $(CFLAGS) $(OBJ_FLAGS) -c $< -o $@
    
    clean :
    	rm -rf $(MAIN_OBJS) $(LIBFT_OBJS) $(PARSE_OBJS)
    
    fclean : clean
    	rm -rf $(NAME)
    
    re : fclean all
    
    .PHONY : all clean fclean re
    최종적으로 수정된 Makefile이다. 목적 파일을 타겟으로 하는 %.o: %.c 구문을 만들어서 목적 파일을 추적해서 자동으로 컴파일 할 때 readline의 라이브러리를 사용하기 위해 필요한 헤더 파일을 포함하도록 옵션을 지정해준다.

간단하게 요약하자면, 라이브러리를 사용하는 프로그램을 Makefile을 통해 컴파일 할 때 목적 파일로 컴파일할 때는 헤더만 포함하게 하고, 목적 파일을 실행 파일로 링킹할 때 라이브러리를 포함하게 해라.

profile
씨앗 개발자

0개의 댓글