source

소스 및 헤더 디렉토리가 별도인 Make 파일을 작성하는 방법?

factcode 2023. 10. 26. 21:51
반응형

소스 및 헤더 디렉토리가 별도인 Make 파일을 작성하는 방법?

이 자습서에 따르면...

저는 소스 파일 2개와 헤더 파일 1개를 가지고 있습니다.자습서처럼 별도의 디렉토리에 배치하고 싶습니다.

그래서 이 프로젝트를 설정했습니다.

.
├── include
│   └── hellomake.h
├── Makefile
└── src
    ├── hellofunc.c
    └── hellomake.c

파일 만들기:

IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)

ODIR=obj
LDIR =../lib

_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ = hellomake.o hellofunc.o 
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)

.PHONY: clean

clean:
    rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ 

제가 생성하는 오류는 다음과 같습니다.

gcc -o hellomake  -I../include
gcc: fatal error: no input files
compilation terminated.
make: *** [hellomake] Error 4

무슨 일입니까?

당신의 튜토리얼은 오래된 관행과 나쁜 관행을 홍보하는 것이므로, 당신은 그것을 피해야 합니다. IMHO.

여기 규칙에 따라:

$(ODIR)/%.o: %.c $(DEPS)

현재 디렉토리에 있는 소스가 실제로 있는 동안 해당 소스를 찾으라는 것입니다.src디렉토리, 따라서 이 패턴은 사용되지 않으며 적합한 패턴이 없습니다.


프로젝트 디렉토리를 다음과 같이 구성해야 합니다.

root
├── include/
│   └── all .h files here
├── lib/
│   └── all third-party library files (.a/.so files) here
├── src/
│   └── all .c files here
└── Makefile

그럼 좋은 방법을 사용해서 차근차근 과정을 살펴보도록 하겠습니다.

첫째, 필요 없다면 아무것도 정의하지 마세요.Make에는 수동으로 작업하기 전에 사용해야 할 미리 정의된 변수와 기능이 많이 있습니다.사실, 그는 너무 많아서 디렉터리에 메이크 파일을 아예 두지 않아도 간단한 파일을 컴파일할 수 있습니다!

  1. 소스 및 빌드 출력 디렉토리를 나열합니다.
SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin # or . if you want it in the current directory
  1. 최종 목표, 즉 실행 파일 이름을 지정합니다.
EXE := $(BIN_DIR)/hellomake
  1. 원본 파일을 나열합니다.
SRC := $(wildcard $(SRC_DIR)/*.c)
  1. 원본 파일에서 개체 파일을 나열합니다.
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
# You can also do it like that
OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC))
  1. 이제 깃발을 다루겠습니다.
CPPFLAGS := -Iinclude -MMD -MP # -I is a preprocessor flag, not a compiler flag
CFLAGS   := -Wall              # some warnings about bad code
LDFLAGS  := -Llib              # -L is a linker flag
LDLIBS   := -lm                # Left empty if no libs are needed

(CPP 여기서 cpreProcessor의 약자이지, cplusPlus가 아닙니다! UseCXXFLAGS및 C++ 플래그의 CXX(C++ 컴파일러의 경우)

플래그는 헤더 종속성을 자동으로 생성하는 데 사용됩니다.나중에 헤더만 변경되면 컴파일을 시작하는 데 사용할 것입니다.

좋아요, 이제 우리 변수가 정확하게 채워졌으니 요리법을 좀 굴려야겠네요.

기본 대상을 호출해야 한다는 의견이 널리 퍼져 있습니다.allMakefile의 첫번째 타겟이 되어야 합니다.그것의 전제조건은 당신이 글만 쓸 때 구축하고자 하는 목표가 되어야 합니다.make명령 줄에 표시됩니다.

all: $(EXE)

하지만 한가지 문제는 Make가 실제로 이름이 붙은 파일이나 폴더를 만들고 싶다고 생각한다는 것입니다.all, 그럼 이건 진짜 목표가 아니라고 전해 드리죠

.PHONY: all

이제 실행 파일을 구축하기 위한 전제 조건을 나열하고, 실행 파일의 레시피를 작성하여 다음 작업을 수행할 수 있도록 하십시오.

$(EXE): $(OBJ)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

(CC C Compiler의 약자입니다.)

참고하세요.$(BIN_DIR)아직 존재하지 않아 컴파일러에 대한 호출이 실패할 수 있습니다.먼저 확인하고 싶다고 말씀드리겠습니다.

$(EXE): $(OBJ) | $(BIN_DIR)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(BIN_DIR):
    mkdir -p $@

몇 가지 빠른 추가 참고 사항:

  • $(CC)는 C에서 컴파일하고 링크할 때 필요한 것을 이미 포함하고 있는 내장 변수입니다.
  • 링커 오류를 방지하기 위해서는 다음과 같이 하는 것이 좋습니다.$(LDFLAGS) 당신의 오브젝트 파일들과.$(LDLIBS) 끝나고
  • $(CPPFLAGS)그리고.$(CFLAGS)여기서는 쓸모가 없고, 컴파일 단계는 이미 끝났고, 여기서는 링크 단계입니다.

다음 단계에서는 원본 파일과 개체 파일이 동일한 접두사를 공유하지 않기 때문에 기본 제공 규칙이 사용자의 특정 경우를 포함하지 않으므로 정확히 수행할 작업을 지정해야 합니다.

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

이전과 같은 문제, 당신의.$(OBJ_DIR)아직 존재하지 않아 컴파일러에 대한 호출이 실패할 수 있습니다.규칙을 업데이트하겠습니다.

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
    mkdir -p $@

좋아요, 이제 실행 파일이 잘 만들어져야 해요.빌드 아티팩트를 청소하는 간단한 규칙을 원합니다.

clean:
    @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) # The @ disables the echoing of the command

(다시 말하지만 clean은 생성할 대상이 아니므로 해당 대상에 추가합니다..PHONY특수 표적!)

마지막으로.자동 종속성 생성에 대해 기억하십니까?GCC와 Clang이 만들 것입니다..d당신의 파일에 해당.o여기에는 우리가 사용할 수 있는 Makefile 규칙이 포함되어 있으므로 여기에 다음을 포함시키겠습니다.

-include $(OBJ:.o=.d) # The dash silences errors when files don't exist (yet)

최종 결과:

SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin

EXE := $(BIN_DIR)/hellomake
SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

CPPFLAGS := -Iinclude -MMD -MP
CFLAGS   := -Wall
LDFLAGS  := -Llib
LDLIBS   := -lm

.PHONY: all clean

all: $(EXE)

$(EXE): $(OBJ) | $(BIN_DIR)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
    mkdir -p $@

clean:
    @$(RM) -rv $(BIN_DIR) $(OBJ_DIR)

-include $(OBJ:.o=.d)

특정한 '대상'이 없는 make 유틸리티는 파일의 첫 번째 대상을 만듭니다.

첫 번째 대상은 보통 '모두'로 명명됩니다.

게시된 파일의 경우 개체 파일을 만들고 명령줄에 대상이 지정되지 않은 경우 실행 파일을 계속 만들지 않습니다.

다음을 제안합니다.

SHELL := /bin/sh

# following so could define executable name on command line
# using the '-D' parameter
#ifndef $(NAME)
    NAME := hellomake
#endif

# use ':=' so macros only evaluated once


MAKE    :=  /usr/bin/make
CC      :=  /usr/bin/gcc

CFLAGS  := -g -Wall -Wextra -pedantic
LFLAGS  :=

ODIR    := obj
IDIR    := ../include
LIBS    :=
LIBPATH := ../lib

DEPS    := $(wildcard $(IDIR)/*.h)
SRCS    := $(wildcard *.c)
OBJS    := $(SRCS:.c=.o)

.PHONY: all
all: $(NAME) $(OBJS)

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) $(CFLAGS) -c -o $@ $< -I$(DEPS)

$(NAME): $(OBJS)
    $(CC) $(LFLAGS) -o $@ $^ -L$(LIBPATH) -l$(LIBS)

.PHONY: clean
clean:
    rm -f $(ODIR)/*.o
    rm -f $(NAME)


however, in your proposed project,
not every source file needs every header file
so should use either gcc or sed to generate the dependency files
then use makefile rules similar to the following,
which may need a little 'tweaking' for your project
because the include files are not in the same directory
as the source files:

DEP := $(SRCS:.c=.d)

#
#create dependency files
#
%.d: %.c 
    # 
    # ========= START $< TO $@ =========
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$;                      \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;     \
    rm -f $@.$$$$
    # ========= END $< TO $@ =========

# 
# compile the .c files into .o files using the compiler flags
#
%.o: %.c %.d 
     # 
     # ========= START $< TO $@ =========
     $(CC) $(CCFLAGS) -c $< -o $@ -I$(IDIR) 
     # ========= END $< TO $@ =========
     # 

# include the contents of all the .d files
# note: the .d files contain:
# <filename>.o:<filename>.c plus all the dependencies for that .c file 
# I.E. the #include'd header files
# wrap with ifneg... so will not rebuild *.d files when goal is 'clean'
#
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DEP)
endif

간단한 Makefile 정의는 당신의 질문에 나타나므로 내가 보기에는 괜찮은 것 같습니다.파일 이름 앞에 컴파일러 옵션을 지정해 보십시오.

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) $(CFLAGS) -c -o $@ $<

hellomake: $(OBJ)
    gcc $(CFLAGS) -o $@ $^

뛰어야 합니다.make소스 디렉토리에서.

이 오류가 발생했을 때"

*gcc: fatal error: no input files
compilation terminated.*

", 즉, 당신은 사물 파일을 가지고 있지 않다는 뜻입니다.

Makefile 에서 " " ${OBJS} :=행을 확인해보세요.

안녕, 형!

당신의 프로젝트라면"helloFunc" 의 아키텍처는 바로 이 점을 좋아합니다.

helloFunc

|

|__Makefile

|__build

|__include

|     |__hellomake.h

|__src

     |__hellofunc.cpp
 
     |__hellomake.cpp
     

Makefile은 다음과 같아야 합니다.

# This is a Makefile for separated multiple sources to build with VSCode on mac
# Thanks, Job Vranish.
# (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
# Reference: Makefile Tutorial
# (https://makefiletutorial.com/)
# Reference: @yagiyuki from Qiita
# (https://qiita.com/yagiyuki/items/ff343d381d9477e89f3b)
# Reference: simonsso from Github
# (https://github.com/simonsso/empty-cpp-project/blob/master/Makefile)
# Reference: Chinese Website blogger CDNS
# (https://blog.csdn.net/qq_22073849/article/details/88893201)

# (1)Compiler
# clang++
CXX = clang++
# (2)Compile options
# -Wall -Wextra -std=c++11 -g
CXX_FLAGS = -Wall -Wextra -std=c++11 -g
# (3)Build task directory path
# I do care about out-of-source builds
# ./build
BUILD_DIR ?= ./build
# (4)Source files directory path
# ./src
SRC_DIRS ?= ./src
# (5)Library files directory path
LIBDIR := 
# (6)Add library files
LIBS :=
# (7)Target file, excutable file.
# main
TARGET ?= main
# (8)Source files(code), to be compiled
# Find source files we want to compile 
# *expression must around by single quotos
# ./src/bank.cpp ./src/main.cpp
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
# (9)Object files
# String substituion for every C/C++ file
# e.g: ./src/bank.cpp turns into ./build/bank.cpp.o
# ./build/bank.cpp.o  ./build/main.cpp.o
OBJS := $(patsubst %.cpp, ${BUILD_DIR}/%.cpp.o, $(notdir $(SRCS)))
# (10)Dependency files
# which will generate a .d file next to the .o file. Then to use the .d files,
# you just need to find them all:
# 
DEPS := $(OBJS:.o=.d)
# (11)Include files directory path
# Every folder in ./src find include files to be passed via clang 
# ./include
INC_DIRS := ./include
# (12)Include files add together a prefix, clang make sense that -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
# (13)Make Makefiles output Dependency files
# That -MMD and -MP flags together to generate Makefiles 
# That generated Makefiles will take .o as .d to the output
# That "-MMD" and "-MP" To generate the dependency files, all you have to do is
# add some flags to the compile command (supported by both Clang and GCC):
CPP_FLAGS ?= $(INC_FLAGS) -MMD -MP
# (14)Link: Generate executable file from object file
# make your target depend on the objects files:
${BUILD_DIR}/${TARGET} : $(OBJS)
    $(CXX) $(OBJS) -o $@ 
# (15)Compile: Generate object files from source files
# $@ := {TARGET}
# $< := THE first file
# $^ all the dependency
# C++ Sources
$(BUILD_DIR)/%.cpp.o: $(SRC_DIRS)/%.cpp
    $(MKDIR_P) $(dir $@)
    $(CXX) $(CPP_FLAGS) $(CXX_FLAGS) -c $< -o $@

#(16)Delete dependence files, object files, and the target file
.PHONY: all clean 
all: ${BUILD_DIR}/${TARGET}


clean:
    $(RM) $(DEPS) $(OBJS) ${BUILD_DIR}/${TARGET}

-include $(DEPS)

MKDIR_P ?= mkdir -p

필요한 Linux 버전으로 만들기 파일 변경:

# (1)Compiler
# g++
CXX = g++
# (2)Compile options
# -Wall -Wextra -std=c++11 -g
CXX_FLAGS = -Wall -Wextra -std=c++11 -g
# (3)Build task directory path
# I do care about out-of-source builds
# ./build
BUILD_DIR ?= ./build
# (4)Source files directory path
# ./src
SRC_DIRS ?= ./src
# (5)Library files directory path
LIBDIR := 
# (6)Add library files
LIBS :=
# (7)Target file, excutable file.
# main
TARGET ?= main
# (8)Source files(code), to be compiled
# Find source files we want to compile 
# *expression must around by single quotos
# ./src/bank.cpp ./src/main.cpp
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
# (9)Object files
# String substituion for every C/C++ file
# e.g: ./src/bank.cpp turns into ./build/bank.cpp.o
# ./build/bank.cpp.o  ./build/main.cpp.o
OBJS := $(patsubst %.cpp, ${BUILD_DIR}/%.cpp.o, $(notdir $(SRCS)))
# (10)Dependency files
# which will generate a .d file next to the .o file. Then to use the .d files,
# you just need to find them all:
# 
DEPS := $(OBJS:.o=.d)
# (11)Include files directory path
# Every folder in ./src find include files to be passed via clang 
# ./include
INC_DIRS := ./include
# (12)Include files add together a prefix, gcc make sense that -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
# (13)Make Makefiles output Dependency files
# That -MMD and -MP flags together to generate Makefiles 
# That generated Makefiles will take .o as .d to the output
# That "-MMD" and "-MP" To generate the dependency files, all you have to do is
# add some flags to the compile command (supported by both Clang and GCC):
CPP_FLAGS ?= $(INC_FLAGS) -MMD -MP
# (14)Link: Generate executable file from object file
# make your target depend on the objects files:
${BUILD_DIR}/${TARGET} : $(OBJS)
    $(CXX) $(OBJS) -o $@ 
# (15)Compile: Generate object files from source files
# $@ := {TARGET}
# $< := THE first file
# $^ all the dependency
# C++ Sources
$(BUILD_DIR)/%.cpp.o: $(SRC_DIRS)/%.cpp
    $(MKDIR_P) $(dir $@)
    $(CXX) $(CPP_FLAGS) $(CXX_FLAGS) -c $< -o $@

#(16)Delete dependency files, object files and the target file
.PHONY: all clean 
all: ${BUILD_DIR}/${TARGET} 
clean:
    $(RM) $(DEPS) $(OBJS) ${BUILD_DIR}/${TARGET}

-include $(DEPS)

MKDIR_P ?= mkdir -p

당신이 주목해야 할 것은 당신의 "Makefile" file은 include files와 source files의 동일한 디렉토리입니다.

그래서 당신은 당신의 것을 바꾸어야 합니다.IDIR:=../include" "IDIR:=./include"로 이동합니다.Makefile".

종료!

윈도우 설정에서 사용하고 있는 내용은 다음과 같습니다.

CC = g++
CFLAGS   = -Wall -std=c++20

SRCDIR   = src
HEADDIR  = include
OBJDIR   = build
BINDIR   = bin
# where the executable will be stored
EXECUTABLE := $(BINDIR)/main

# list of all source files
SOURCES  := $(wildcard $(SRCDIR)/*.cpp)
# list of all header files
INCLUDES := $(wildcard $(HEADDIR)/*.h)
# from the list of all source files, create a list of all object files
OBJECTS  := $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)

# all: clean $(EXECUTABLE)
all: $(EXECUTABLE)


# Link: Generate executable file from object file
$(EXECUTABLE): $(OBJECTS)
    @echo LINKING..... $(CC) -o $@ $(OBJECTS)
    @$(CC) -o $@ $(OBJECTS)
    @echo RUNNING: $(EXECUTABLE)
    @$(EXECUTABLE)
# Compile: Generate object files from source files
# $@ := {EXECUTABLE}
# $< := THE first file
# $^ all the dependency
# C++ Sources
$(OBJDIR)/%.o : $(SRCDIR)/%.cpp | makedirs
    @echo COMPILING... $(CC) $(CFLAGS) -c "$<" -o "$@"
    @$(CC) $(CFLAGS) -c $< -o $@

# `|` is order-only-prerequisites
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html
makedirs:
# check if the file exists; if not, create it
# mkdir -p $(OBJDIR) in linux
    @if not exist "$(OBJDIR)" mkdir $(OBJDIR)
    @if not exist "$(BINDIR)" mkdir $(BINDIR)

#Delete dependence files, object files, and the EXECUTABLE file
clean:
    @echo CLEANING UP
# check if the directories exist; if so, delete them
    @if exist "$(OBJDIR)" rmdir /s /q $(OBJDIR)
    @if exist "$(BINDIR)" rmdir /s /q $(BINDIR)

언급URL : https://stackoverflow.com/questions/30573481/how-to-write-a-makefile-with-separate-source-and-header-directories

반응형