Makefile: คำจำกัดความหลายคำและข้อผิดพลาดในการอ้างอิงที่ไม่ได้กำหนด

ขณะนี้ฉันกำลังเรียนรู้วิธีการเขียนโค้ดโดยไม่ต้องใช้ IDE และฉันกำลังเรียนรู้วิธีเขียน makefiles นี่คือโครงการทดสอบปัจจุบันของฉัน:

\__ /CoDstructor/
      |\__ Makefile
      |\__ /bin/
      |      \__ CoDstructor.exe
      |\__ /src/
      |     \__ /cod/
      |          |\__ main.cpp
      |          |\__ types.cpp
      |           \__ types.hpp
       \__ /obj/
            \__ /cod/
                 |\__ main.o
                 |\__ main.d
                 |\__ types.o
                  \__ types.d

ฉันมี makefile ระดับบนสุดเพียงไฟล์เดียวเท่านั้นที่จัดการทุกโมดูลในไดเร็กทอรี src/ และสร้างอ็อบเจ็กต์และไฟล์อ้างอิงในไดเร็กทอรี obj/ นี่คือไฟล์:

main.cpp

#include <iostream>
#include <cod/types.hpp>

int main() {
    int s;
    std::cin >> s;
    return lol();
}

ประเภท.hpp

#ifndef TYPES_HPP_INCLUDED
#define TYPES_HPP_INCLUDED

int lol();

#endif // TYPES_HPP_INCLUDED

ประเภท.cpp

#include <iostream>
#include <cod/types.hpp>

int lol() {
    std::cout << "lol";

    return 0;
}

เมคไฟล์

APP_NAME = CoDstructor
DEBUG_TARGET = debug
RELEASE_TARGET = release
SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin
INC_DIR = src

INCLUDE_DIRS +=

LIBRARY_DIRS +=

CXXFLAGS += -Wall
CXXFLAGS += -Werror
CXXFLAGS += -Wextra
CXXFLAGS += -pedantic
CXXFLAGS += -std=c++11

$(DEBUG_TARGET): CXXFLAGS += -g
$(RELEASE_TARGET): CXXFLAGS += -O3

LDFLAGS += -static
LDFLAGS += -static-libstdc++
LDFLAGS += -static-libgcc

$(DEBUG_TARGET): LDFLAGS += -g
$(RELEASE_TARGET): LDFLAGS +=

CPPMACROS =

$(DEBUG_TARGET): CPPMACROS += DEBUG
$(RELEASE_TARGET): CPPMACROS += NDEBUG

CXXFLAGS += $(foreach i,$(INC_DIR),$(addprefix -I,$(i)))
CXXFLAGS += $(foreach i,$(INCLUDE_DIRS),$(addprefix -I,$(i)))
CXXFLAGS += $(foreach i,$(CPPMACROS),$(addprefix -D,$(i)))

LIBS = $(foreach i,$(LIBRARY_DIRS),$(addprefix -L,$(i)))

SOURCES =  $(subst ./,,$(shell find . -name *.cpp))
OBJS = $(subst $(SRC_DIR),$(OBJ_DIR),$(SOURCES:.cpp=.o))

DEPS = $(OBJS:.o=.d)

all: $(DEBUG_TARGET) clean

$(RELEASE_TARGET): $(BIN_DIR)/$(APP_NAME) clean
    @echo Building release...

$(DEBUG_TARGET): $(BIN_DIR)/$(APP_NAME) clean
    @echo Building debug...

$(OBJS): $(SOURCES)
    @mkdir -p $(@D)
    $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@

$(BIN_DIR)/$(APP_NAME): $(OBJS)
    $(CXX) $(LDFLAGS) $^ -o $@ $(LIBS)
    @echo $^

.PHONY: clean

clean:
    @echo clean
#   @-rmdir $(OBJ_DIR)

-include $(DEPS)

และนี่คือข้อผิดพลาด:

obj/cod/types.o: In function `main':
D:\PROJECTS\CoDstructor/src/cod/main.cpp:4: multiple definition of `main'
obj/cod/main.o:D:\PROJECTS\CoDstructor/src/cod/main.cpp:4: first defined here
obj/cod/main.o:main.cpp:(.text+0x2a): undefined reference to `lol()'
obj/cod/types.o:main.cpp:(.text+0x2a): undefined reference to `lol()'
collect2.exe: error: ld returned 1 exit status

ฉันค้นหามาเกือบสองวันแล้วว่าจะแก้ไขข้อผิดพลาดเหล่านี้ได้อย่างไร

สำหรับอันแรก (คำจำกัดความหลักหลายคำ): ฉันได้ตรวจสอบตัวแปร OBJS แล้ว และมันมีและลิงก์เพียงครั้งเดียวที่ main.o แล้วทำไมถึงบอกว่า obj/cod/types.o ในบรรทัดแรกล่ะ? ไม่มีหลักใน types.hpp/types.cpp

ข้อผิดพลาดที่สอง (การอ้างอิงที่ไม่ได้กำหนดถึง lol()): เหตุใดการอ้างอิงจึงไม่ได้กำหนดไว้ แต่ไม่มีข้อผิดพลาดของคอมไพเลอร์แทน

ข้อผิดพลาดที่สาม (สร้างใหม่ทุกอย่างทุกครั้ง แทนที่จะแก้ไขเฉพาะข้อผิดพลาดหรือแทนที่จะค้นหาไฟล์อ้างอิง .d)

ฉันกำลังใช้งาน MinGW32 build ล่าสุด (g++ 4.9.1) และ MSYS ล่าสุด (make)

ฉันทำอะไรผิดที่นี่?


person nonsensation    schedule 01.08.2014    source แหล่งที่มา
comment
คำสั่งคอมไพล์ที่ล้มเหลวคืออะไร?   -  person hyde    schedule 01.08.2014
comment
ฉันกำลังใช้โครงสร้างที่ยอดเยี่ยม: { "cmd":["make"], "path": "D:/DEV/MinGW/w32/mingw32/bin/;D:/DEV/msys/1.0/bin", "shell": true, }   -  person nonsensation    schedule 01.08.2014


คำตอบ (3)


ปัญหาสำคัญของคุณอยู่ที่นี่:

$(OBJS): $(SOURCES)
   @mkdir -p $(@D)
   $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@

กฎข้อนี้มีสองปัญหา

  1. คุณมีไฟล์ออบเจ็กต์หลายไฟล์ การดำเนินการแรกจะไม่ทำงานเมื่อคุณทำมากกว่าการมีไดเร็กทอรีย่อยเพียงไดเร็กทอรีเดียวภายใต้ไดเร็กทอรี src ของคุณ

  2. การกระทำที่สองจะคอมไพล์ src/cod/main.cpp ของคุณสองครั้ง ครั้งแรกเป็น obj/cod/main.o แล้วจึงรวบรวมเป็น obj/cod/types.o นั่นเป็นเพราะว่า $< เป็นรายการแรกในรายการการขึ้นต่อกัน และรายการแรกคือ src/cod/main.cpp

ปัญหาที่สองแก้ไขได้ง่ายกว่าครั้งแรก คุณต้องมีกฎรูปแบบ:

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
   $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@

ตอนนี้เพื่อแก้ไขปัญหาแรก จะเกิดอะไรขึ้นถ้าคุณมีไดเร็กทอรีต้นทางหลายไดเร็กทอรี โดยแต่ละไดเร็กทอรีย่อยของไดเร็กทอรี src ของคุณล่ะ คุณต้องการสร้างไดเร็กทอรีย่อย obj ที่สอดคล้องกันสำหรับแต่ละรายการ โปรดทราบว่ามีไดเร็กทอรีที่คุณไม่ได้สร้าง นั่นคือไดเร็กทอรี bin สิ่งแรกที่ต้องทำคือสร้างรายการไดเร็กทอรีที่คุณต้องการสร้าง

MKDIRS = $(sort $(foreach i,$(OBJS),$(dir $i)))
MKDIRS += bin

ถ้าอย่างนั้นคุณต้องมีกฎเพื่อสร้างมัน มาเริ่มกันง่ายๆ:

mkdirs:
   mkdir -p $(MKDIRS)

อย่างไรก็ตามเรื่องนี้เป็นปัญหา คุณจะได้รับข้อความแสดงข้อผิดพลาดจาก mkdir และบิลด์จะหยุดหากมีไดเร็กทอรีใดไดเร็กทอรีเหล่านั้นอยู่แล้ว เราจำเป็นต้องสร้างไดเร็กทอรีเหล่านั้นเฉพาะในกรณีที่ไม่มีอยู่ Make จัดเตรียมเครื่องมือในการกรองรายการนั้นให้เหลือเฉพาะไดเร็กทอรีที่ไม่มีอยู่ แต่ฉันไม่อยากทำเช่นนั้น สำหรับฉัน การใช้เชลล์ในการตัดสินใจจะดีกว่า:

mkdirs:
   @sh -c \
     'for d in $(MKDIRS); do \
        if [ ! -d $$d ]; then echo mkdir -p $$d; mkdir -p $$d; fi \
      done'

ตอนนี้เราต้องเพิ่มกฎนั้นเป็นการพึ่งพา

$(RELEASE_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
   @echo Release built

$(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
   @echo Debug built

โปรดทราบว่าฉันได้ทำสามสิ่งกับเป้าหมายเหล่านั้นแล้ว

  1. ฉันเพิ่มเป้าหมาย mkdirs แล้ว

  2. ฉันลบเป้าหมาย clean แล้ว คุณไม่อยากทำอย่างนั้นที่นี่จริงๆ มันขัดต่อวัตถุประสงค์ของการรวบรวมแยกกัน เมื่อคุณมีไฟล์ต้นฉบับหลายร้อยไฟล์และทำการเปลี่ยนแปลงไฟล์ใดไฟล์หนึ่ง คุณเพียงแค่ต้องการคอมไพล์ไฟล์ต้นฉบับนั้นใหม่ จากนั้นสร้างไฟล์ปฏิบัติการขึ้นใหม่จากไฟล์อ็อบเจ็กต์หลายร้อยไฟล์ที่มีอยู่แล้ว อย่าทำความสะอาดทันทีหลังจากที่คุณสร้างไฟล์ปฏิบัติการ! คุณสามารถดำเนินการ make clean จากบรรทัดคำสั่งได้ตลอดเวลาหากคุณรู้สึกว่าจำเป็นต้องทำเช่นนั้น

  3. ฉันเปลี่ยนข้อความ ข้อความเหล่านั้นจะออกหลังจากพอใจการขึ้นต่อกันแล้ว พวกเขาจะเป็นสิ่งสุดท้ายที่คุณเห็น หากต้องการรับข้อความก่อนเริ่มดำเนินการ วิธีที่ง่ายที่สุดคือสร้างเป้าหมายปลอมเพื่อพิมพ์ข้อความที่ต้องการ

บันทึกสุดท้ายสองสามข้อ:

โปรดทราบว่า mkdirs เป็นเป้าหมายปลอม (และ all ก็เช่นกัน) ทางที่ดีควรเพิ่มลงในรายการ .PHONY ของคุณ และรายการนั้นควรอยู่ด้านหน้าสุด

สุดท้าย เป้าหมาย $(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME) ก็คือระเบิดเวลา เช่นเดียวกับ $(RELEASE_TARGET) วันหนึ่งคุณจะพบข้อมูลเกี่ยวกับการทำคู่ขนาน ไม่มีการรับประกันว่าไดเร็กทอรีจะถูกสร้างขึ้นก่อนที่คอมไพเลอร์จะพยายามคอมไพล์โค้ด ไดเร็กทอรีจะไม่มีอยู่ และ kaboom การสร้างของคุณล้มเหลว การทำให้ makefile ของคุณแข็งแกร่งต่อการดำเนินการแบบขนานนั้นเป็นเรื่องของคำถาม stackexchange ที่แตกต่างกัน

person David Hammen    schedule 01.08.2014
comment
ขอบคุณมาก! ฉันทำตามคำอธิบายของคุณแล้ว และมันก็ใช้งานได้แล้ว! - person nonsensation; 01.08.2014
comment
และขอขอบคุณสำหรับการแก้ไข clean ฉันได้มันไปที่นั่นในขณะที่ทดสอบ makefile ของฉัน เพื่อดูว่ามันทำสิ่งที่ฉันอยากทำจริงๆ หรือไม่ - person nonsensation; 01.08.2014
comment
@Serthy: ฉันแก้ไขควบคู่ไปกับการยอมรับและความคิดเห็นของคุณ ฉันเพิ่มย่อหน้าสุดท้ายเกี่ยวกับการสร้างแบบขนาน คุณต้องระมัดระวังกับการขึ้นต่อกันใน make แบบขนาน การระบุเป้าหมาย mkdir เป็นการขึ้นต่อกันหนึ่งในวิธีทั่วไปที่สุดในการทำให้ขนานล้มเหลว - person David Hammen; 01.08.2014
comment
ขอบคุณ! ฉันสงสัยว่ามี/เหตุใดจึงไม่มีวิธีที่ง่ายกว่าในการแทนที่ make ฉันมีไฟล์ปฏิบัติการธรรมดาหรือไฟล์แบตช์ (พร้อมไวยากรณ์ที่ง่ายกว่าและมีข้อเสียน้อยกว่าตามที่คุณพูดถึงการสร้างแบบขนาน) ในใจว่าทำสิ่งที่จำเป็นเท่านั้น - person nonsensation; 01.08.2014

กฎ $(OBJS): $(SOURCES) ของคุณไม่ใช่สิ่งที่คุณคิด ด้วยเหตุนี้ คุณกำลังสร้างทั้ง main.o และ types.o จากไฟล์ main.cpp เดียวกัน ($< พารามิเตอร์ในบรรทัดคำสั่ง) ดังนั้นคุณจึงมีไฟล์ที่เหมือนกันสองไฟล์ซึ่งขัดแย้งกัน ในขณะที่ types.cpp ไม่ได้ถูกสร้างขึ้นด้วยซ้ำ

กฎที่ถูกต้องจะเป็น $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp

person keltar    schedule 01.08.2014
comment
ขอบคุณสำหรับคำอธิบาย อย่างไรก็ตาม เมื่อใช้ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp จะใช้เฉพาะไฟล์ในไดเร็กทอรีระดับบนสุดเท่านั้น และไดเร็กทอรีย่อย ฯลฯ จะถูกละเว้นใช่ไหม - person nonsensation; 01.08.2014

จากหน่วยความจำ ไม่ได้ตรวจสอบเอกสาร ปัญหาคือ:

$(OBJS): $(SOURCES)
    @mkdir -p $(@D)
    $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@

นั่นหมายความว่าไฟล์อ็อบเจ็กต์ทุกไฟล์ขึ้นอยู่กับแหล่งที่มาทั้งหมด จากนั้นคำสั่งคอมไพล์จะใช้ $< ซึ่งฉันคิดว่าหมายถึงการขึ้นต่อกันครั้งแรก ดังนั้น คุณคอมไพล์ทั้ง types.o และ main.o จาก main.cpp (ซึ่งเป็นอันแรกของ $(SOURCES) ฉันคิดว่า)


วิธีแก้ไขประการหนึ่งคือใช้ กฎรูปแบบ บางอย่างเช่น:

 %.o : %.c
     @mkdir -p $(@D)
     $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@

ไฟล์อ็อบเจ็กต์ได้รับการกำหนดโดยกฎ $(BIN_DIR)/$(APP_NAME): $(OBJS) ของคุณแล้ว และกฎรูปแบบนี้จะบอกวิธีสร้างไฟล์เหล่านั้น

person hyde    schedule 01.08.2014
comment
ขอบคุณ คำตอบของคุณก็ได้รับการชื่นชมอย่างมากเช่นกัน! - person nonsensation; 01.08.2014