Panduan Penting untuk Qmake

Diterbitkan: 2022-03-11

pengantar

qmake adalah alat sistem pembangunan yang dikirimkan bersama pustaka Qt yang menyederhanakan proses pembangunan di berbagai platform. Tidak seperti CMake dan Qbs , qmake adalah bagian dari Qt sejak awal dan harus dianggap sebagai alat "asli". Tak perlu dikatakan, IDE default Qt— Qt Creator —memiliki dukungan terbaik dari qmake. Ya, Anda juga dapat memilih sistem build CMake dan Qbs untuk proyek baru di sana, tetapi ini tidak terintegrasi dengan baik. Sepertinya dukungan CMake di Qt Creator akan ditingkatkan dari waktu ke waktu, dan ini akan menjadi alasan yang baik untuk menerbitkan edisi kedua panduan ini, yang ditujukan khusus untuk CMake. Bahkan jika Anda tidak bermaksud menggunakan Qt Creator, Anda mungkin masih ingin mempertimbangkan qmake sebagai sistem pembangunan kedua jika Anda sedang membangun perpustakaan umum atau plugin. Hampir semua perpustakaan atau plugin berbasis Qt pihak ketiga menyediakan file qmake yang digunakan untuk berintegrasi ke dalam proyek berbasis qmake dengan mulus. Hanya sedikit dari mereka yang menyediakan konfigurasi ganda, misalnya qmake dan CMake. Anda mungkin lebih suka menggunakan qmake jika hal berikut berlaku untuk Anda:

  • Anda sedang membangun proyek berbasis Qt lintas platform
  • Anda menggunakan Qt Creator IDE dan sebagian besar fiturnya
  • Anda sedang membangun perpustakaan/plugin mandiri untuk digunakan oleh proyek qmake lainnya

Panduan ini menjelaskan fitur qmake yang paling berguna dan memberikan contoh nyata untuk masing-masing fitur tersebut. Pembaca yang baru mengenal Qt dapat menggunakan panduan ini sebagai tutorial untuk membangun sistem Qt. Pengembang Qt dapat memperlakukan ini sebagai buku masak saat memulai proyek baru atau dapat secara selektif menerapkan beberapa fitur ke salah satu proyek yang ada dengan dampak rendah.

Ilustrasi proses pembuatan qmake

Penggunaan Qmake Dasar

Spesifikasi qmake ditulis dalam file .pro (“project”). Ini adalah contoh file .pro yang paling sederhana:

 SOURCES = hello.cpp

Secara default, ini akan membuat Makefile yang akan membuat file yang dapat dieksekusi dari file kode sumber tunggal hello.cpp .

Untuk membangun biner (dapat dieksekusi dalam kasus ini), Anda harus menjalankan qmake terlebih dahulu untuk menghasilkan Makefile dan kemudian make (atau nmake , atau mingw32-make tergantung pada rantai alat Anda) untuk membangun target.

Singkatnya, spesifikasi qmake tidak lebih dari daftar definisi variabel yang dicampur dengan pernyataan aliran kontrol opsional. Setiap variabel, secara umum, menyimpan daftar string. Pernyataan aliran kontrol memungkinkan Anda untuk menyertakan file spesifikasi qmake lainnya, mengontrol bagian kondisional, dan bahkan memanggil fungsi.

Memahami Sintaks Variabel

Saat mempelajari proyek qmake yang ada, Anda mungkin terkejut bagaimana variabel yang berbeda dapat dirujuk: \(VAR,\){VAR} atau $$(VAR)

Gunakan lembar contekan mini ini sambil menerapkan aturan:

  • VAR = value Tetapkan nilai ke VAR
  • VAR += value Tambahkan nilai ke daftar VAR
  • VAR -= value Hapus nilai dari daftar VAR
  • $$VAR or $${VAR} Mendapatkan nilai VAR pada saat qmake dijalankan
  • $(VAR) Isi dari VAR Lingkungan pada saat Makefile (bukan qmake) sedang berjalan
  • $$(VAR) Isi dari VAR Lingkungan pada saat qmake (bukan Makefile) sedang berjalan

Template Umum

Daftar lengkap variabel qmake dapat ditemukan di spesifikasi: http://doc.qt.io/qt-5/qmake-variable-reference.html

Mari kita tinjau beberapa template umum untuk proyek:

 # 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

Cukup tambahkan SOURCES += … dan HEADERS += … untuk membuat daftar semua file kode sumber Anda, dan selesai.

Sejauh ini, kami telah meninjau template yang sangat mendasar. Proyek yang lebih kompleks biasanya mencakup beberapa sub-proyek dengan ketergantungan satu sama lain. Mari kita lihat bagaimana mengelola ini dengan qmake.

Sub-proyek

Kasus penggunaan yang paling umum adalah aplikasi yang dikirimkan dengan satu atau beberapa perpustakaan dan proyek pengujian. Perhatikan struktur berikut:

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

Jelas, kami ingin dapat membangun semuanya sekaligus, seperti ini:

 cd project qmake && make

Untuk mencapai tujuan ini, kita memerlukan file proyek qmake di bawah folder /project :

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

CATATAN: menggunakan CONFIG += ordered dianggap sebagai praktik yang buruk—lebih suka menggunakan .depends sebagai gantinya.

Spesifikasi ini menginstruksikan qmake untuk membangun sub-proyek perpustakaan terlebih dahulu karena target lain bergantung padanya. Kemudian ia dapat membangun library-tests dan aplikasi dalam urutan arbitrer karena keduanya saling bergantung.

Struktur direktori proyek

Menghubungkan Perpustakaan

Dalam contoh di atas, kami memiliki perpustakaan yang perlu ditautkan ke aplikasi. Di C/C++, ini berarti kita perlu mengkonfigurasi beberapa hal lagi:

  1. Tentukan -I untuk menyediakan jalur pencarian untuk arahan #include.
  2. Tentukan -L untuk menyediakan jalur pencarian untuk linker.
  3. Tentukan -l untuk menyediakan perpustakaan apa yang perlu ditautkan.

Karena kami ingin semua sub-proyek dapat dipindahkan, kami tidak dapat menggunakan jalur absolut atau relatif. Misalnya, kita tidak boleh melakukan ini: INCLUDEPATH += ../library/include dan tentu saja kita tidak dapat mereferensikan biner perpustakaan (.a file) dari folder build sementara. Mengikuti prinsip "pemisahan masalah", kita dapat dengan cepat menyadari bahwa file proyek aplikasi harus abstrak dari detail perpustakaan. Sebaliknya, perpustakaan bertanggung jawab untuk memberi tahu di mana menemukan file header, dll.

Mari manfaatkan direktif include() qmake untuk menyelesaikan masalah ini. Dalam proyek perpustakaan, kami akan menambahkan spesifikasi qmake lain dalam file baru dengan ekstensi .pri (ekstensi bisa apa saja, tapi di sini i singkatan dari include). Jadi, perpustakaan akan memiliki dua spesifikasi: library.pro dan library.pri . Yang pertama digunakan untuk membangun perpustakaan, yang kedua digunakan untuk menyediakan semua detail yang dibutuhkan oleh proyek yang sedang dikonsumsi.

Isi file library.pri adalah sebagai berikut:

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

BASEDIR menentukan folder proyek perpustakaan (tepatnya, lokasi file spesifikasi qmake saat ini, yaitu library.pri dalam kasus kami). Seperti yang Anda duga, INCLUDEPATH akan dievaluasi ke /project/library/include . DESTDIR adalah direktori tempat sistem build menempatkan artefak keluaran, seperti (file .o .a .so .dll atau .exe). Ini biasanya dikonfigurasi di IDE Anda, jadi Anda tidak boleh berasumsi di mana file output berada.

Di file application.pro cukup tambahkan include(../library/library.pri) dan selesai.

Mari kita tinjau bagaimana proyek aplikasi dibangun dalam kasus ini:

  1. Project.pro teratas adalah project.pro subdir. Ini memberitahu kita bahwa proyek perpustakaan perlu dibangun terlebih dahulu. Jadi qmake masuk ke folder library dan membangunnya menggunakan library.pro . Pada tahap ini, library.a diproduksi dan ditempatkan ke dalam folder DESTDIR .
  2. Kemudian qmake masuk ke subfolder aplikasi dan mem-parsing file application.pro . Ia menemukan direktif include(../library/library.pri) , yang memerintahkan qmake untuk membaca dan menafsirkannya segera. Ini menambahkan definisi baru ke variabel INCLUDEPATH dan LIBS , jadi sekarang kompiler dan tautan tahu di mana harus mencari file yang disertakan, binari perpustakaan, dan perpustakaan apa yang akan ditautkan.

Kami melewatkan pembangunan proyek pengujian perpustakaan, tetapi itu identik dengan proyek aplikasi. Jelas, proyek pengujian kami juga perlu menautkan perpustakaan yang seharusnya diuji.

Dengan pengaturan ini, Anda dapat dengan mudah memindahkan proyek perpustakaan ke proyek qmake lain dan memasukkannya, dengan demikian merujuk ke file .pri . Ini persis bagaimana perpustakaan pihak ketiga didistribusikan oleh komunitas.

config.pri

Sangat umum untuk proyek yang kompleks memiliki beberapa parameter konfigurasi bersama yang digunakan oleh banyak sub-proyek. Untuk menghindari duplikasi, Anda dapat kembali memanfaatkan direktif include() dan membuat config.pri di folder tingkat atas. Anda mungkin juga memiliki "utilitas" qmake umum yang dibagikan ke sub-proyek Anda, mirip dengan apa yang akan kita bahas selanjutnya dalam panduan ini.

Menyalin Artefak ke DESTDIR

Seringkali, proyek memiliki beberapa file "lain" yang perlu didistribusikan bersama dengan perpustakaan atau aplikasi. Kami hanya perlu dapat menyalin semua file tersebut ke DESTDIR selama proses pembuatan. Perhatikan cuplikan berikut:

 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) }

Catatan: Dengan menggunakan pola ini, Anda dapat menentukan fungsi Anda sendiri yang dapat digunakan kembali yang berfungsi pada file.

Tempatkan kode ini ke /project/copyToDestDir.pri sehingga Anda dapat include() dalam menuntut sub-proyek sebagai berikut:

 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)

Catatan: DISTFILES diperkenalkan untuk tujuan yang sama, tetapi hanya berfungsi di Unix.

Pembuatan Kode

Contoh yang bagus dari pembuatan kode sebagai langkah yang dibuat sebelumnya adalah ketika proyek C++ menggunakan protobuf Google. Mari kita lihat bagaimana kita bisa menyuntikkan eksekusi protoc ke dalam proses build.

Anda dapat dengan mudah mencari solusi yang sesuai di Google, tetapi Anda harus menyadari satu kasus sudut penting. Bayangkan Anda memiliki dua kontrak, di mana A mereferensikan B.

 A.proto <= B.proto

Jika kita akan membuat kode untuk A.proto terlebih dahulu (untuk menghasilkan A.pb.h dan A.pb.cxx ) dan memasukkannya ke kompilator, itu hanya akan gagal karena ketergantungan B.pb.h belum ada. Untuk mengatasi ini, kita harus melewati semua tahap pembuatan kode proto sebelum membangun kode sumber yang dihasilkan.

Saya menemukan cuplikan yang bagus untuk tugas ini di sini: https://github.com/jmesmon/qmake-protobuf-example/blob/master/protobuf.pri

Ini adalah skrip yang cukup besar, tetapi Anda harus sudah tahu cara menggunakannya:

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

Saat melihat ke protobuf.pri , Anda mungkin melihat pola umum yang dapat dengan mudah diterapkan ke kompilasi khusus atau pembuatan kode:

 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

Lingkup dan Ketentuan

Seringkali, kita perlu mendefinisikan deklarasi khusus untuk platform tertentu, seperti Windows atau MacOS. Qmake menawarkan tiga indikator platform yang telah ditentukan: win32, macx, dan unix. Berikut sintaksnya:

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

Lingkup dapat bersarang, dapat menggunakan operator ! , | dan bahkan wildcard:

 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 }

Catatan: Unix didefinisikan pada Mac OS! Jika Anda ingin menguji Mac OS (bukan Unix generik), gunakan kondisi unix:!macx .

Di Qt Creator, kondisi cakupan debug dan release tidak berfungsi seperti yang diharapkan. Untuk membuatnya bekerja dengan benar, gunakan pola berikut:

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

Fungsi yang Berguna

Qmake memiliki sejumlah fungsi tertanam yang menambahkan lebih banyak otomatisasi.

Contoh pertama adalah fungsi files() . Dengan asumsi Anda memiliki langkah pembuatan kode yang menghasilkan sejumlah variabel file sumber. Berikut adalah bagaimana Anda dapat memasukkan semuanya ke dalam SOURCES :

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

Ini akan menemukan semua file dengan ekstensi .c di sub-folder yang generated dan menambahkannya ke variabel SOURCES .

Contoh kedua mirip dengan yang sebelumnya, tetapi sekarang pembuatan kode menghasilkan file teks yang berisi nama file keluaran (daftar file):

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

Ini hanya akan membaca konten file dan memperlakukan setiap baris sebagai entri untuk SOURCES .

Catatan: Daftar lengkap fungsi yang disematkan dapat ditemukan di sini: http://doc.qt.io/qt-5/qmake-function-reference.html

Memperlakukan Peringatan sebagai Kesalahan

Cuplikan berikut menggunakan fitur cakupan bersyarat yang dijelaskan sebelumnya:

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

Alasan komplikasi ini adalah karena MSVC memiliki flag yang berbeda untuk mengaktifkan opsi ini.

Menghasilkan Versi Git

Cuplikan berikut berguna saat Anda perlu membuat definisi praprosesor yang berisi versi SW saat ini yang diperoleh dari Git:

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

Ini berfungsi pada platform apa pun selama perintah git tersedia. Jika Anda menggunakan tag Git, maka ini akan mengintip tag terbaru, meskipun cabangnya duluan. Ubah perintah git describe untuk mendapatkan output pilihan Anda.

Kesimpulan

Qmake adalah alat hebat yang berfokus untuk membangun proyek berbasis Qt lintas platform Anda. Dalam panduan ini, kami meninjau penggunaan alat dasar dan pola yang paling umum digunakan yang akan membuat struktur proyek Anda fleksibel dan spesifikasi build mudah dibaca dan dipelihara.

Ingin mempelajari cara membuat aplikasi Qt Anda terlihat lebih baik? Coba: Cara Mendapatkan Bentuk Sudut Bulat Dalam C++ Menggunakan Kurva Bezier dan QPainter: Panduan Langkah demi Langkah