สร้างเทียบกับสร้างรหัสตัวอย่าง?


120

ฉันสงสัยว่ามีโค้ดตัวอย่างสำหรับMakefiles ( make) และCMakeLists.txt( cmake) ที่ทั้งสองทำสิ่งเดียวกัน (ข้อแตกต่างเพียงอย่างเดียวคือรหัสที่เขียนขึ้นmakeและอีกอันในcmake)

ฉันพยายามมองหา 'cmake vs make' แต่ไม่พบการเปรียบเทียบโค้ดใด ๆ การทำความเข้าใจความแตกต่างจะเป็นประโยชน์มากแม้ว่าจะเป็นเพียงกรณีธรรมดา ๆ ก็ตาม


20
+1 นี่เป็นคำถามที่ดี เมื่อฉันเริ่มต้นcmakeก็ต้องการสิ่งนี้เช่นกัน แต่ฉันสงสัยว่าคุณจะพบเพราะความสามารถนั้นไม่ได้จับคู่ความสามารถนั้นกับอีกด้านหนึ่ง ถ้าคุณพยายามcmakeทำตัวเหมือนmakeคุณจะทำให้ตัวเองบ้าคลั่งอย่างจริงจัง ดีที่สุดเพียงเริ่มต้นจากศูนย์ สิ่งที่เป็นเรื่องเล็กน้อยmakeมีส่วนเกี่ยวข้องค่อนข้างมากcmakeและในทางกลับกัน
Ernest Friedman-Hill

1
@ ErnestFriedman-Hill คุณมีรายละเอียดเพิ่มเติมหรือไม่? ดังนั้นmakeและcmakeมีความแตกต่างกันมากจนควรมองว่าเป็นส่วนเสริมแทนที่จะเป็นเครื่องมือที่แข่งขันกัน?
Ehtesh Choudhury

3
@ ชูราเน - cmake ไม่ได้สร้างอะไรขึ้นมาเอง จะสร้าง Makefiles (และสคริปต์บิลด์อื่น ๆ ที่คล้ายกัน) ซึ่งคุณจะเรียกใช้ ดังนั้นเมื่อใดก็ตามที่คุณเขียนไฟล์ cmake คุณต้องคิดว่าควรใช้คำสั่งในช่วงเวลาสร้างหรือในช่วงเวลาสร้าง การดำเนินการบางอย่างเช่นคัดลอกชุดไฟล์ที่ใช้สัญลักษณ์แทนในเวลาสร้างค่อนข้างซับซ้อนเมื่อเทียบกับ " cp *.x $(OUTDIR)" ที่คุณเขียนใน Makefile บางทีส่วนที่น่ารำคาญที่สุดสำหรับฉันก็คือ Makefiles ที่สร้างขึ้นนั้นมาจากการออกแบบที่ไม่สามารถพกพาได้อย่างสมบูรณ์และไม่ยืดหยุ่น (ต่อ)
Ernest Friedman-Hill

1
(ต่อ) คุณไม่สามารถแม้แต่ย้ายไดเร็กทอรีต้นทางบนเครื่องเดียวกันโดยไม่ต้องเรียกใช้ cmake ซ้ำเพื่อสร้าง Makefile ใหม่! ดังนั้นทางเลือกจึงไม่ใช่ระหว่าง cmake และ make แต่เป็นระหว่างการเขียน Makefiles แบบพกพาด้วยตัวคุณเองหรือใช้ cmake เพื่อสร้างไฟล์ที่ไม่สามารถพกพาได้ในเครื่อง build แต่ละเครื่อง (และเนื่องจากคุณสามารถใช้ Cygwin หรือ mingw บน Windows ได้ฉันมักจะพบว่าอดีตนั้นง่ายกว่า )
Ernest Friedman-Hill

1
เป็นคำถามที่ดี แต่ไม่มีคำตอบที่เฉพาะเจาะจงเนื่องจากเครื่องมือทั้งสองพยายามแก้ปัญหาที่แตกต่างกัน cmake รับข้อมูลเกี่ยวกับวิธีการสร้างโปรแกรมสร้าง makefiles ที่สร้างโปรแกรม ดังนั้น cmake จึงเป็นภาษาที่มีกฎการสร้างนามธรรมและ gnu make เป็นการแก้ไขการพึ่งพาที่รันโปรแกรมบนการส่งผ่านกราฟแบบ acyclic ที่กำหนดทิศทาง
Alex

คำตอบ:


119

Makefile ต่อไปนี้สร้างปฏิบัติการชื่อจากแหล่งที่มาprog เชื่อมโยงกับ และทั้งสองสร้างขึ้นจากแหล่งที่มา นอกจากนี้การใช้ห้องสมุดในและส่วนหัวของมันใน โดยค่าเริ่มต้น Makefile จะสร้างเป้าหมายการเผยแพร่ แต่ยังมีเป้าหมายการดีบัก:prog1.c, prog2.c, prog3.c and main.cproglibmystatlib.alibmydynlib.soproglibstuff.astuff/libstuff/include

#Makefile    
CC = gcc
CPP = g++
RANLIB = ar rcs
RELEASE = -c -O3 
DEBUG = -c -g -D_DEBUG
INCDIR = -I./stuff/include
LIBDIR = -L./stuff/lib -L.
LIBS = -lstuff -lmystatlib -lmydynlib
CFLAGS = $(RELEASE)

PROGOBJS = prog1.o prog2.o prog3.o

prog: main.o $(PROGOBJS) mystatlib mydynlib
    $(CC) main.o $(PROGOBJS) $(LIBDIR) $(LIBS) -o prog 
debug: CFLAGS=$(DEBUG)
debug: prog

mystatlib: mystatlib.o
    $(RANLIB) libmystatlib.a mystatlib.o
mydynlib: mydynlib.o
    $(CPP) -shared mydynlib.o -o libmydynlib.so

%.o: %.c
    $(CC) $(CFLAGS) $(INCDIR) $< -o $@ 
%.o: %.cpp
    $(CPP) $(CFLAGS) $(INCDIR) -fPIC  $< -o $@ 

นี่คือสิ่งCMakeLists.txtที่ (เกือบ) เหมือนกันทุกประการโดยมีความคิดเห็นบางส่วนเพื่อขีดเส้นใต้ความคล้ายคลึงกับ Makefile:

#CMakeLists.txt     
cmake_minimum_required(VERSION 2.8)                    # stuff not directly
project(example)                                       # related to building

include_directories(${CMAKE_SOURCE_DIR}/stuff/include) # -I flags for compiler
link_directories(${CMAKE_SOURCE_DIR}/stuff/lib)        # -L flags for linker

set(PROGSRC prog1.c prog2.c prog3.c)                   # define variable 

add_executable(prog main.c ${PROGSRC})                 # define executable target prog, specify sources
target_link_libraries(prog mystatlib mydynlib stuff)   # -l flags for linking prog target

add_library(mystatlib STATIC mystatlib.c)              # define static library target mystatlib, specify sources

add_library(mydynlib SHARED mydynlib.cpp)              # define shared library target mydynlib, specify sources
#extra flags for linking mydynlib
set_target_properties(mydynlib PROPERTIES POSITION_INDEPENDENT_CODE TRUE) 
#alternatively:
#set_target_properties(mydynlib PROPERTIES COMPILE_FLAGS "-fPIC")

ในตัวอย่างง่ายๆนี้ความแตกต่างที่สำคัญที่สุดคือ:

  • CMake รับรู้ว่าคอมไพเลอร์ใดที่จะใช้สำหรับซอร์สประเภทใด นอกจากนี้ยังเรียกใช้ลำดับคำสั่งที่ถูกต้องสำหรับเป้าหมายแต่ละประเภท ดังนั้นไม่มีข้อกำหนดที่ชัดเจนของคำสั่งเช่น$(CC) ..., $(RANLIB) ...และอื่น ๆ

  • แฟล็กคอมไพลเลอร์ / ลิงค์เกอร์ตามปกติทั้งหมดที่เกี่ยวข้องกับการรวมไฟล์ส่วนหัวไลบรารี ฯลฯ จะถูกแทนที่ด้วยคำสั่งอิสระของแพลตฟอร์ม / สร้างระบบ

  • ธงแก้จุดบกพร่องที่จะถูกรวมโดยการตั้งค่าตัวแปรCMAKE_BUILD_TYPEที่จะ "แก้ปัญหา" หรือโดยการส่งผ่านไปยัง CMake cmake -DCMAKE_BUILD_TYPE:STRING=Debugเมื่อเรียกใช้โปรแกรม:

  • นอกจากนี้ CMake ยังมีการรวมแฟล็ก '-fPIC' ที่เป็นอิสระจากแพลตฟอร์ม (ผ่านPOSITION_INDEPENDENT_CODEคุณสมบัติ) และอื่น ๆ อีกมากมาย ยังสามารถใช้การตั้งค่าที่คลุมเครือได้มากขึ้นด้วยมือใน CMake เช่นเดียวกับใน Makefile (โดยใช้COMPILE_FLAGS และคุณสมบัติที่คล้ายกัน) แน่นอนว่า CMake เริ่มเปล่งประกายเมื่อไลบรารีของบุคคลที่สาม (เช่น OpenGL) รวมอยู่ในลักษณะพกพา

  • ขั้นตอนการสร้างมีขั้นตอนเดียวหากคุณใช้ Makefile คือพิมพ์ makeที่บรรทัดคำสั่ง สำหรับ CMake มีสองขั้นตอน: ขั้นแรกคุณต้องตั้งค่าสภาพแวดล้อมการสร้างของคุณ (ไม่ว่าจะโดยการพิมพ์cmake <source_dir>ในไดเร็กทอรีบิลด์ของคุณหรือโดยการเรียกใช้ไคลเอนต์ GUI บางตัว) สิ่งนี้จะสร้าง Makefile หรือสิ่งที่เทียบเท่าขึ้นอยู่กับระบบการสร้างที่คุณเลือก (เช่นสร้างบน Unixes หรือ VC ++ หรือ MinGW + Msys บน Windows) ระบบสร้างสามารถส่งผ่านไปยัง CMake เป็นพารามิเตอร์ อย่างไรก็ตาม CMake เป็นตัวเลือกเริ่มต้นที่เหมาะสมขึ้นอยู่กับการกำหนดค่าระบบของคุณ ประการที่สองคุณทำการสร้างจริงในระบบบิลด์ที่เลือก

แหล่งที่มาและคำแนะนำการสร้างที่มีอยู่ที่https://github.com/rhoelzel/make_cmake


3
Makefile ไม่ซับซ้อนเกินไป? การใช้CPPFLAGSแทนกันINCDIRอาจใช้กฎในตัวและการเรียกใช้คอมไพเลอร์อย่างชัดเจนจะซ้ำซ้อน ในทำนองเดียวกันสำหรับการจัดการ ar กฎในตัวสามารถครอบคลุมสิ่งนั้นได้เช่นกัน นอกจากนี้ทำไมต้องตั้งค่าCPPและCCชัดเจน? พวกมันถูกกำหนดให้เป็นค่าที่ดีอยู่แล้วโดยmakeเป็นตัวแปรที่กำหนดไว้ล่วงหน้า makeนอกจากนี้ยังรับรู้ว่าคอมไพเลอร์ที่จะใช้สำหรับซอร์สประเภทใดกฎในตัวมีมากมาย
Christian Hujer

1
และหลายตัวแปรเหล่านี้ควรจะได้รับมอบหมายกับแทน:= =
Christian Hujer

1
มองไปที่คำอธิบายcmakeก็เปรียบมากขึ้นเพื่อกว่าautomake make
ivan_pozdeev

Makefile ที่ให้มาอาจลดลงเหลือ 3/4 บรรทัด คุณควรระบุแทนINCLUDES INCDIRคุณไม่ต้องการกฎ% .o:%. c
shuva

7

หยิบซอฟต์แวร์บางตัวที่ใช้ CMake เป็นระบบสร้าง (มีโครงการโอเพนซอร์ซมากมายให้เลือกเป็นตัวอย่าง) รับซอร์สโค้ดและกำหนดค่าโดยใช้ CMake อ่าน makefiles ผลลัพธ์และสนุก

สิ่งหนึ่งที่ควรทราบว่าเครื่องมือเหล่านั้นไม่ได้จับคู่แบบตัวต่อตัว ความแตกต่างที่ชัดเจนที่สุดคือ CMake จะสแกนหาการอ้างอิงระหว่างไฟล์ต่างๆ (เช่นส่วนหัว C และไฟล์ต้นฉบับ) ในขณะที่สร้างใบนั้นให้กับผู้เขียน makefile


4

หากคำถามนี้เกี่ยวกับMakefileผลลัพธ์ตัวอย่างของCMakeList.txtไฟล์โปรดตรวจสอบแหล่งที่มาของ cmake-backend และสร้างขึ้นMakefileมา หากไม่ใช่การเพิ่มการตอบกลับของ @Roberto ฉันพยายามทำให้ง่ายโดยซ่อนรายละเอียด

ฟังก์ชัน CMake

ในขณะที่Makeเป็นเครื่องมือที่ยืดหยุ่นสำหรับกฎและสูตรอาหารCMakeเป็นชั้นของนามธรรมที่เพิ่มคุณสมบัติการกำหนดค่าด้วย

ธรรมดาของฉันCMakeLists.txtจะมีลักษณะดังต่อไปนี้

cmake_minimum_required(VERSION 2.8)
project(example)
file(GLOB testapp_SOURCES *.cc)
add_executable(testapp ${testapp_SOURCES})

โปรดทราบว่าการCMakeซ่อนhowโครงสร้างสามารถทำได้ เราระบุเพียงwhatอินพุตและเอาต์พุต

มีรายชื่อของฟังก์ชั่นการโทรที่ถูกกำหนดโดยCMakeLists.txtcmake

(ฟังก์ชัน CMake) Vs สร้างกฎ

ในจะถูกนำมาใช้แทน นอกเหนือจากคุณสมบัติที่เหมือนกันแล้วให้มีการผูกมัด มินิมอลลิสต์ของฉันจะมีลักษณะดังต่อไปนี้Makefilerules and recipesfunctionsfunctionrules and recipesMakefile

-include "executable.mk"
TARGETS=testapp.bin
all:${TARGETS}

ในขณะที่executable.mkจะมีลักษณะดังต่อไปนี้

SOURCES=$(wildcard *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)
DEPS=$(SOURCES:.cpp=.d)

%.bin:$(OBJECTS)
    $(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(LIBS)

.PHONY: all clean

clean:
    $(RM) $(OBJECTS) $(DEPS) $(TARGETS)

-include $(DEPS)

เริ่มจากจุดเริ่มต้นฉันจะเริ่มต้นด้วยสิ่งMakefileต่อไปนี้

all: testapp.bin

testapp.bin:sourcea.o sourcb.o
    $(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(LIBS)

.PHONY: all clean

clean:
    $(RM) $(OBJECTS) testapp.bin

ฉันได้รับตัวอย่างนี้จากที่นี่และแก้ไข โปรดทราบว่ามีการเพิ่มกฎโดยนัยบางอย่างลงในไฟล์นี้ซึ่งสามารถพบได้ในเอกสาร makefile ตัวแปรโดยนัยบางตัวก็เกี่ยวข้องเช่นกัน

โปรดทราบว่าMakefileมีรายละเอียดที่recipeแสดงhowการสร้างที่สามารถทำได้ เป็นไปได้ที่จะเขียนexecutable.mkเพื่อเก็บรายละเอียดที่กำหนดไว้ในไฟล์เดียว ด้วยวิธีนี้ makefile สามารถลดลงได้ตามที่ฉันแสดงไว้ก่อนหน้านี้

ตัวแปรภายในในCMakeและMake

ตอนนี้เริ่มขั้นสูงแล้วCMakeเราสามารถตั้งค่าสถานะคอมไพเลอร์ดังต่อไปนี้

set(CMAKE_C_FLAGS "-Wall")

โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับCMakeตัวแปรเริ่มต้นในCMakeCache.txtไฟล์ CMakeโค้ดข้างต้นจะเทียบเท่ากับMakeรหัสด้านล่าง

CFLAGS = -Wall

ทราบว่าCFLAGSเป็นตัวแปรภายในในMakeทางเดียวกันเป็นตัวแปรภายในCMAKE_C_FLAGSCMake

เพิ่มเส้นทางรวมและไลบรารีใน CMake

เราสามารถทำได้ในการcmakeใช้ฟังก์ชัน

target_include_directories(testapp PRIVATE "myincludes")
list(APPEND testapp_LIBRARIES
    mytest mylibrarypath
)
target_link_libraries(testapp ${testapp_LIBRARIES})

เทียบกับการเพิ่มการรวมและเส้นทางไลบรารีใน Make

เราสามารถเพิ่มรวมและไลบรารีได้โดยเพิ่มบรรทัดดังต่อไปนี้

INCLUDES += -Imyincludes
LIBS += -Lmylibrarypath -lmytest

โปรดทราบว่าบรรทัดด้านบนนี้สามารถสร้างได้จาก auto-gen tools หรือ pkg-config (แม้ว่า Makefile จะไม่ขึ้นอยู่กับเครื่องมือกำหนดค่าอัตโนมัติ)

CMake กำหนดค่า / สองครั้ง

โดยปกติคุณสามารถสร้างconfig.hไฟล์บางไฟล์ได้เช่นเดียวกับauto-configเครื่องมือโดยใช้configure_fileฟังก์ชัน เป็นไปได้ที่จะใช้ฟังก์ชันที่กำหนดเองในการเขียนหลอกมากขึ้น และในที่สุดเราก็สามารถเลือก config ดังต่อไปนี้

cmake --build . --config "Release"

เป็นไปได้ที่จะเพิ่มตัวเลือกที่กำหนดค่าได้โดยใช้optionฟังก์ชัน

Makefile กำหนดค่า / ปรับแต่ง

หากเราจำเป็นต้องคอมไพล์ด้วยแฟล็ก debug เราสามารถเรียกสิ่งที่makeคล้ายกัน

make CXXFLAGS=NDEBUG

ฉันคิดว่าตัวแปรภายในMakefile-rulesและCMake-functionsเป็นการเริ่มต้นที่ดีสำหรับการเปรียบเทียบขอให้โชคดีกับการขุดเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.