คู่มือสำคัญสำหรับ Qmake

เผยแพร่แล้ว: 2022-03-11

บทนำ

qmake เป็นเครื่องมือระบบบิลด์ที่มาพร้อมกับไลบรารี Qt ซึ่งทำให้กระบวนการบิลด์ในแพลตฟอร์มต่างๆ ง่ายขึ้น ไม่เหมือนกับ CMake และ Qbs qmake เป็นส่วนหนึ่งของ Qt ตั้งแต่เริ่มต้นและจะถือเป็นเครื่องมือ "ดั้งเดิม" ไม่จำเป็นต้องพูด IDE เริ่มต้นของ Qt— ผู้สร้าง Qt—ได้รับการสนับสนุนที่ดีที่สุดของ qmake ทันทีที่แกะกล่อง ได้ คุณยังสามารถเลือกระบบบิลด์ CMake และ Qbs สำหรับโปรเจ็กต์ใหม่ได้ที่นั่น แต่ระบบเหล่านี้ไม่ได้ผสานรวมเป็นอย่างดี มีแนวโน้มว่าการสนับสนุน CMake ใน Qt Creator จะดีขึ้นเมื่อเวลาผ่านไป และนี่จะเป็นเหตุผลที่ดีในการออกคู่มือฉบับที่สองนี้โดยมุ่งเป้าไปที่ CMake โดยเฉพาะ แม้ว่าคุณจะไม่ได้ตั้งใจที่จะใช้ Qt Creator คุณก็อาจยังต้องการพิจารณา qmake เป็นระบบบิลด์ที่สองในกรณีที่คุณกำลังสร้างไลบรารีสาธารณะหรือปลั๊กอิน ไลบรารีหรือปลั๊กอินที่ใช้ Qt ของบุคคลที่สามแทบทั้งหมดจัดหาไฟล์ qmake ที่ใช้ในการรวมเข้ากับโปรเจ็กต์ที่ใช้ qmake ได้อย่างราบรื่น มีเพียงไม่กี่ตัวเท่านั้นที่ให้การกำหนดค่าแบบคู่ เช่น qmake และ CMake คุณอาจต้องการใช้ qmake หากสิ่งต่อไปนี้ใช้ได้กับคุณ:

  • คุณกำลังสร้างโปรเจ็กต์ที่ใช้ Qt แบบข้ามแพลตฟอร์ม
  • คุณกำลังใช้ Qt Creator IDE และฟีเจอร์ส่วนใหญ่อยู่
  • คุณกำลังสร้างไลบรารี/ปลั๊กอินแบบสแตนด์อโลนเพื่อใช้โดยโปรเจ็กต์ qmake อื่น

คู่มือนี้อธิบายคุณสมบัติ qmake ที่มีประโยชน์ที่สุดและให้ตัวอย่างในโลกแห่งความเป็นจริงสำหรับแต่ละรายการ ผู้อ่านที่ยังใหม่กับ Qt สามารถใช้คู่มือนี้เป็นบทช่วยสอนเกี่ยวกับระบบบิลด์ของ Qt นักพัฒนาของ Qt สามารถปฏิบัติต่อสิ่งนี้เสมือนเป็นตำราอาหารเมื่อเริ่มโครงการใหม่ หรือสามารถเลือกใช้คุณสมบัติบางอย่างกับโครงการที่มีอยู่ซึ่งมีผลกระทบน้อย

ภาพประกอบของกระบวนการสร้าง qmake

การใช้งาน Qmake ขั้นพื้นฐาน

ข้อกำหนด qmake เขียนในไฟล์ . .pro (“project”) นี่คือตัวอย่างไฟล์ .pro ที่ง่ายที่สุด:

 SOURCES = hello.cpp

โดยค่าเริ่มต้น สิ่งนี้จะสร้าง Makefile ที่จะสร้างไฟล์เรียกทำงานจากไฟล์ซอร์สโค้ดเดียว hello.cpp

ในการสร้างไบนารี (เรียกใช้งานได้ในกรณีนี้) คุณต้องเรียกใช้ qmake ก่อนเพื่อสร้าง Makefile จากนั้นจึง make (หรือ nmake หรือ mingw32-make ขึ้นอยู่กับ toolchain ของคุณ) เพื่อสร้างเป้าหมาย

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

การทำความเข้าใจไวยากรณ์ของตัวแปร

เมื่อเรียนรู้โครงการ qmake ที่มีอยู่ คุณอาจแปลกใจว่าสามารถอ้างอิงตัวแปรต่างๆ ได้อย่างไร: \(VAR,\){VAR} หรือ $$(VAR)

ใช้สูตรโกงย่อนี้ในขณะที่รับกฎ:

  • VAR = value กำหนดค่าให้ VAR
  • VAR += value ต่อท้ายค่า VAR list
  • VAR -= value ลบค่าออกจากรายการ VAR
  • $$VAR หรือ $${VAR} รับค่าของ VAR ในขณะที่ qmake กำลังทำงาน
  • $(VAR) เนื้อหาของ Environment VAR ในขณะที่ Makefile (ไม่ใช่ qmake) กำลังทำงาน
  • $$(VAR) เนื้อหาของ Environment VAR ณ เวลาที่ qmake (ไม่ใช่ Makefile) กำลังทำงาน

เทมเพลตทั่วไป

รายการตัวแปร qmake ทั้งหมดสามารถพบได้ในข้อมูลจำเพาะ: http://doc.qt.io/qt-5/qmake-variable-reference.html

มาทบทวนแม่แบบทั่วไปสองสามอย่างสำหรับโครงการกัน:

 # Windows application TEMPLATE = app CONFIG += windows # Shared library (.so or .dll) TEMPLATE = lib CONFIG += shared # Static library (.a or .lib) TEMPLATE = lib CONFIG += static # Console application TEMPLATE = app CONFIG += console

เพียงเพิ่ม SOURCES += … และ HEADERS += … เพื่อแสดงรายการไฟล์ซอร์สโค้ดทั้งหมดของคุณ เท่านี้ก็เสร็จเรียบร้อย

จนถึงตอนนี้ เราได้ตรวจสอบเทมเพลตพื้นฐานแล้ว โปรเจ็กต์ที่ซับซ้อนมากขึ้นมักจะรวมโปรเจ็กต์ย่อยหลายโปรเจ็กต์ที่มีการพึ่งพาซึ่งกันและกัน เรามาดูวิธีจัดการสิ่งนี้ด้วย qmake

โครงการย่อย

กรณีการใช้งานที่พบบ่อยที่สุดคือแอปพลิเคชันที่มาพร้อมกับไลบรารีหนึ่งหรือหลายรายการและโครงการทดสอบ พิจารณาโครงสร้างต่อไปนี้:

 /project ../library ..../include ../library-tests ../application

แน่นอน เราต้องการที่จะสร้างทุกอย่างได้ในคราวเดียว ดังนี้:

 cd project qmake && make

เพื่อให้บรรลุเป้าหมายนี้ เราจำเป็นต้องมีไฟล์โครงการ qmake ภายใต้โฟลเดอร์ /project :

 TEMPLATE = subdirs SUBDIRS = library library-tests application library-tests.depends = library application.depends = library

หมายเหตุ: การใช้ CONFIG += ordered ถือเป็นแนวปฏิบัติที่ไม่ดี—โปรดใช้ . .depends แทน

ข้อมูลจำเพาะนี้แนะนำให้ qmake สร้างโปรเจ็กต์ย่อยของไลบรารีก่อน เนื่องจากเป้าหมายอื่นขึ้นอยู่กับโปรเจ็กต์ดังกล่าว จากนั้นจึงสามารถสร้าง library-tests และแอปพลิเคชันในลำดับใดก็ได้เนื่องจากทั้งสองสิ่งนี้ต้องพึ่งพาอาศัยกัน

โครงสร้างไดเร็กทอรีโครงการ

การเชื่อมโยงห้องสมุด

ในตัวอย่างข้างต้น เรามีไลบรารี่ที่ต้องเชื่อมโยงกับแอปพลิเคชัน ใน C/C++ นี่หมายความว่าเราจำเป็นต้องมีการกำหนดค่าเพิ่มเติมสองสามอย่าง:

  1. ระบุ -I เพื่อให้เส้นทางการค้นหาสำหรับคำสั่ง #include
  2. ระบุ -L เพื่อให้เส้นทางการค้นหาสำหรับตัวเชื่อมโยง
  3. ระบุ -l เพื่อระบุสิ่งที่ต้องเชื่อมโยงไลบรารี

เนื่องจากเราต้องการให้โครงการย่อยทั้งหมดสามารถเคลื่อนย้ายได้ เราจึงไม่สามารถใช้เส้นทางแบบสัมบูรณ์หรือแบบสัมพัทธ์ได้ ตัวอย่างเช่น เราจะไม่ทำเช่นนี้: INCLUDEPATH += ../library/include และแน่นอนว่าเราไม่สามารถอ้างอิงไบนารีของไลบรารี (.a ไฟล์) จากโฟลเดอร์บิลด์ชั่วคราวได้ ตามหลักการ "การแยกข้อกังวล" เราสามารถรู้ได้อย่างรวดเร็วว่าไฟล์โครงการแอปพลิเคชันจะต้องเป็นนามธรรมจากรายละเอียดห้องสมุด แต่เป็นความรับผิดชอบของห้องสมุดที่จะต้องบอกว่าจะค้นหาไฟล์ส่วนหัวได้ที่ไหน ฯลฯ

มาใช้ประโยชน์จากคำสั่ง include() ของ qmake เพื่อแก้ปัญหานี้ ในโครงการห้องสมุด เราจะเพิ่มข้อกำหนด qmake อื่นในไฟล์ใหม่ที่มีนามสกุล .pri (ส่วนขยายสามารถเป็นอะไรก็ได้ แต่ในที่นี้ i หมายถึงรวม) ดังนั้น ห้องสมุดจะมีข้อกำหนดสองประการ: library.pro และ library.pri อันแรกใช้ในการสร้างห้องสมุด อันที่สองใช้เพื่อให้รายละเอียดทั้งหมดที่จำเป็นโดยโปรเจ็กต์ที่ใช้งานมาก

เนื้อหาของไฟล์ library.pri จะเป็นดังนี้:

 LIBTARGET = library BASEDIR = $${PWD} INCLUDEPATH *= $${BASEDIR}/include LIBS += -L$${DESTDIR} -llibrary

BASEDIR ระบุโฟลเดอร์ของโปรเจ็กต์ไลบรารี (ตามจริงแล้ว ตำแหน่งของไฟล์ข้อมูลจำเพาะ qmake ปัจจุบัน ซึ่งก็คือ library.pri ในกรณีของเรา) อย่างที่คุณอาจเดาได้ INCLUDEPATH จะถูกประเมินเป็น /project/library/include DESTDIR คือไดเร็กทอรีที่ระบบบิลด์วางอาร์ติแฟกต์เอาต์พุต เช่น (ไฟล์ .o .a .so .dll หรือ .exe) โดยปกติจะมีการกำหนดค่าใน IDE ของคุณ ดังนั้นคุณไม่ควรตั้งสมมติฐานว่าไฟล์เอาต์พุตอยู่ที่ใด

ในไฟล์ application.pro เพียงเพิ่ม include(../library/library.pri) เสร็จแล้ว

มาทบทวนกันถึงวิธีการสร้างโปรเจ็กต์แอปพลิเคชันในกรณีนี้:

  1. Topmost project.pro เป็นโครงการย่อย มันบอกเราว่าต้องสร้างโครงการห้องสมุดก่อน ดังนั้น qmake จะเข้าสู่โฟลเดอร์ของไลบรารีและสร้างโดยใช้ library.pro ในขั้นตอนนี้ library.a ถูกผลิตและวางไว้ในโฟลเดอร์ DESTDIR
  2. จากนั้น qmake จะเข้าสู่โฟลเดอร์ย่อยของแอปพลิเคชันและแยกวิเคราะห์ไฟล์ application.pro พบคำสั่ง include(../library/library.pri) ซึ่งสั่งให้ qmake อ่านและตีความทันที สิ่งนี้จะเพิ่มคำจำกัดความใหม่ให้กับตัวแปร INCLUDEPATH และ LIBS ดังนั้นตอนนี้คอมไพเลอร์และลิงเกอร์จึงรู้ว่าจะค้นหาไฟล์ที่รวมได้จากที่ใด ไบนารีของไลบรารี และไลบรารีใดที่จะเชื่อมโยง

เราข้ามการสร้างโครงการทดสอบห้องสมุด แต่เหมือนกับโครงการแอปพลิเคชัน แน่นอน โครงการทดสอบของเราจะต้องเชื่อมโยงไลบรารีที่ควรจะทดสอบด้วย

ด้วยการตั้งค่านี้ คุณสามารถย้ายโปรเจ็กต์ไลบรารีไปยังโปรเจ็กต์ qmake อื่นได้อย่างง่ายดายและรวมโปรเจ็กต์เข้าด้วยกัน ดังนั้นจึงเป็นการอ้างถึงไฟล์ . .pri นี่คือวิธีการแจกจ่ายไลบรารีของบุคคลที่สามโดยชุมชน

config.pri

เป็นเรื่องปกติมากสำหรับโปรเจ็กต์ที่ซับซ้อนที่จะมีพารามิเตอร์คอนฟิกูเรชันที่ใช้ร่วมกันซึ่งถูกใช้โดยโปรเจ็กต์ย่อยจำนวนมาก เพื่อหลีกเลี่ยงความซ้ำซ้อน คุณสามารถใช้คำสั่ง include() อีกครั้งและสร้าง config.pri ในโฟลเดอร์ระดับบนสุดได้ คุณอาจมี "ยูทิลิตี้" ทั่วไปของ qmake ที่แชร์กับโปรเจ็กต์ย่อยของคุณ คล้ายกับที่เราพูดถึงต่อไปในคู่มือนี้

การคัดลอกสิ่งประดิษฐ์ไปยัง DESTDIR

บ่อยครั้งที่โปรเจ็กต์มีไฟล์ "อื่นๆ" บางไฟล์ที่ต้องแจกจ่ายไปพร้อมกับไลบรารีหรือแอปพลิเคชัน เราเพียงแค่ต้องสามารถคัดลอกไฟล์ดังกล่าวทั้งหมดไปยัง DESTDIR ระหว่างกระบวนการสร้างได้ พิจารณาตัวอย่างต่อไปนี้:

 defineTest(copyToDestDir) { files = $$1 for(FILE, files) { DDIR = $$DESTDIR FILE = $$absolute_path($$FILE) # Replace slashes in paths with backslashes for Windows win32:FILE ~= s,/,\\,g win32:DDIR ~= s,/,\\,g QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR) $$escape_expand(\\n\\t) } export(QMAKE_POST_LINK) }

หมายเหตุ: เมื่อใช้รูปแบบนี้ คุณจะกำหนดฟังก์ชันที่นำกลับมาใช้ใหม่ได้ซึ่งทำงานกับไฟล์ได้

วางรหัสนี้ลงใน /project/copyToDestDir.pri เพื่อให้คุณสามารถ include() ในโครงการย่อยที่ต้องการได้ดังต่อไปนี้:

 include(../copyToDestDir.pri) MYFILES += \ parameters.conf \ testdata.db ## this is copying all files listed in MYFILES variable copyToDestDir($$MYFILES) ## this is copying a single file, a required DLL in this example copyToDestDir($${3RDPARTY}/openssl/bin/crypto.dll)

หมายเหตุ: DISTFILES ถูกนำมาใช้เพื่อจุดประสงค์เดียวกัน แต่ใช้งานได้ใน Unix เท่านั้น

การสร้างรหัส

ตัวอย่างที่ดีของการสร้างโค้ดเป็นขั้นตอนที่สร้างไว้ล่วงหน้าคือเมื่อโปรเจ็กต์ C++ ใช้ Google protobuf เรามาดูกันว่าเราจะฉีด protoc Execution เข้าไปในกระบวนการสร้างได้อย่างไร

คุณสามารถใช้ Google โซลูชันที่เหมาะสมได้อย่างง่ายดาย แต่คุณต้องตระหนักถึงกรณีที่สำคัญหนึ่งกรณี ลองนึกภาพคุณมีสัญญาสองฉบับ โดยที่ A กำลังอ้างอิง B

 A.proto <= B.proto

หากเราจะสร้างรหัสสำหรับ A.proto ก่อน (เพื่อสร้าง A.pb.h และ A.pb.cxx ) และป้อนไปยังคอมไพเลอร์ มันจะล้มเหลวเพราะการพึ่งพา B.pb.h ยังไม่มีอยู่ เพื่อแก้ปัญหานี้ เราต้องผ่านขั้นตอนการสร้างโค้ดโปรโตทั้งหมดก่อนที่จะสร้างซอร์สโค้ดที่เป็นผลลัพธ์

ฉันพบตัวอย่างที่ดีสำหรับงานนี้ที่นี่: https://github.com/jmesmon/qmake-protobuf-example/blob/master/protobuf.pri

เป็นสคริปต์ที่ค่อนข้างใหญ่ แต่คุณควรรู้วิธีใช้งานอยู่แล้ว:

 PROTOS = A.proto B.proto include(protobuf.pri)

เมื่อดูใน protobuf.pri คุณอาจสังเกตเห็นรูปแบบทั่วไปที่สามารถนำไปใช้กับการคอมไพล์แบบกำหนดเองหรือการสร้างโค้ดได้อย่างง่ายดาย:

 my_custom_compiler.name = my custom compiler name my_custom_compiler.input = input variable (list) my_custom_compiler.output = output file path + pattern my_custom_compiler.commands = custom compilation command my_custom_compiler.variable_out = output variable (list) QMAKE_EXTRA_COMPILERS += my_custom_compiler

ขอบเขตและเงื่อนไข

บ่อยครั้ง เราต้องกำหนดการประกาศเฉพาะสำหรับแพลตฟอร์มที่กำหนด เช่น Windows หรือ MacOS Qmake มีตัวบ่งชี้แพลตฟอร์มที่กำหนดไว้ล่วงหน้าสามตัว: win32, macx และ unix นี่คือไวยากรณ์:

 win32 { # add Windows application icon, not applicable to unix/macx platform RC_ICONS += icon.ico }

สามารถซ้อนขอบเขตได้ ใช้ตัวดำเนินการ ! , | และแม้กระทั่งสัญลักษณ์แทน:

 macx:debug { # include only on Mac and only for debug build HEADERS += debugging.h } win32|macx { HEADERS += windows_or_macx.h } win32-msvc* { # same as win32-msvc|win32-mscv.net }

หมายเหตุ: Unix ถูกกำหนดไว้บน Mac OS! หากคุณต้องการทดสอบ Mac OS (ไม่ใช่ Unix ทั่วไป) ให้ใช้เงื่อนไข unix:!macx

ใน Qt Creator เงื่อนไขขอบเขตการ debug และ release ไม่ทำงานตามที่คาดไว้ เพื่อให้ทำงานได้อย่างถูกต้อง ให้ใช้รูปแบบต่อไปนี้:

 CONFIG(debug, debug|release) { LIBS += ... } CONFIG(release, debug|release) { LIBS += ... }

ฟังก์ชั่นที่มีประโยชน์

Qmake มีฟังก์ชันฝังตัวจำนวนมากที่เพิ่มการทำงานอัตโนมัติมากขึ้น

ตัวอย่างแรกคือฟังก์ชัน files() สมมติว่าคุณมีขั้นตอนการสร้างโค้ดที่สร้างจำนวนตัวแปรของไฟล์ต้นฉบับ ต่อไปนี้คือวิธีที่คุณสามารถรวมไว้ใน SOURCES ได้:

 SOURCES += $$files(generated/*.c)

ซึ่งจะค้นหาไฟล์ทั้งหมดที่มีนามสกุล .c ในโฟลเดอร์ย่อยที่ generated และเพิ่มลงในตัวแปร SOURCES

ตัวอย่างที่สองคล้ายกับก่อนหน้านี้ แต่ตอนนี้ การสร้างโค้ดสร้างไฟล์ข้อความที่มีชื่อไฟล์เอาต์พุต (รายการไฟล์):

 SOURCES += $$cat(generated/filelist, lines)

นี่จะเป็นการอ่านเนื้อหาไฟล์และถือว่าแต่ละบรรทัดเป็นรายการสำหรับ SOURCES

หมายเหตุ: รายการฟังก์ชันฝังตัวทั้งหมดมีอยู่ที่นี่: http://doc.qt.io/qt-5/qmake-function-reference.html

การปฏิบัติต่อคำเตือนเป็นข้อผิดพลาด

ข้อมูลโค้ดต่อไปนี้ใช้คุณลักษณะขอบเขตตามเงื่อนไขที่อธิบายไว้ก่อนหน้านี้:

 *g++*: QMAKE_CXXFLAGS += -Werror *msvc*: QMAKE_CXXFLAGS += /WX

สาเหตุของภาวะแทรกซ้อนนี้เป็นเพราะ MSVC มีการตั้งค่าสถานะที่แตกต่างกันเพื่อเปิดใช้งานตัวเลือกนี้

กำลังสร้างเวอร์ชัน Git

ข้อมูลโค้ดต่อไปนี้มีประโยชน์เมื่อคุณต้องการสร้างคำจำกัดความของตัวประมวลผลล่วงหน้าที่มีเวอร์ชัน SW ปัจจุบันที่ได้รับจาก Git:

 DEFINES += SW_VERSION=\\\"$$system(git describe --always --abbrev=0)\\\"

ใช้งานได้กับทุกแพลตฟอร์มตราบใดที่คำสั่ง git พร้อมใช้งาน หากคุณใช้แท็ก Git แท็กนี้จะดูแท็กล่าสุด แม้ว่าสาขาจะเดินหน้าต่อไป แก้ไขคำสั่ง git describe เพื่อรับผลลัพธ์ที่คุณต้องการ

บทสรุป

Qmake เป็นเครื่องมือที่ยอดเยี่ยมที่เน้นการสร้างโปรเจ็กต์ที่ใช้ Qt ข้ามแพลตฟอร์มของคุณ ในคู่มือนี้ เราได้ตรวจสอบการใช้งานเครื่องมือพื้นฐานและรูปแบบที่ใช้บ่อยที่สุด ซึ่งจะทำให้โครงสร้างโครงการของคุณยืดหยุ่นและสร้างข้อกำหนดที่ง่ายต่อการอ่านและบำรุงรักษา

ต้องการเรียนรู้วิธีทำให้แอป Qt ของคุณดูดีขึ้นหรือไม่ ลอง: วิธีรับรูปร่างมุมโค้งมนใน C ++ โดยใช้ Bezier Curves และ QPainter: คำแนะนำทีละขั้นตอน