Dapatkan baris perintah kompiler C++ lengkap

Di CMake, flag untuk compiler C++ dapat dipengaruhi dengan berbagai cara: menyetel CMAKE_CXX_FLAGS secara manual, menggunakan add_definitions(), memaksakan standar C++ tertentu, dan seterusnya.

Untuk mengkompilasi target dalam proyek yang sama dengan aturan yang berbeda (header yang sudah dikompilasi, dalam kasus saya), saya perlu mereproduksi perintah persis yang digunakan untuk mengkompilasi file yang ditambahkan oleh perintah seperti add_executable() di direktori ini.

Membaca CMAKE_CXX_FLAGS hanya mengembalikan nilai yang disetel secara eksplisit, CMAKE_CXX_FLAGS_DEBUG dan saudara kandungnya hanya mencantumkan opsi Debug/Rilis default. Ada fungsi khusus untuk mengambil flag dari add_definitions() dan add_compiler_options(), tetapi sepertinya tidak ada yang bisa mengembalikan baris perintah terakhir.

Bagaimana saya bisa meneruskan semua flag ke kompiler ke dalam variabel CMake?


person Fabian Knorr    schedule 04.03.2016    source sumber


Jawaban (4)


Untuk menjawab pertanyaan saya sendiri: Sepertinya satu-satunya cara untuk mendapatkan semua flag kompiler adalah dengan merekonstruksinya dari berbagai sumber. Kode yang saya kerjakan sekarang adalah sebagai berikut (untuk GCC):

macro (GET_COMPILER_FLAGS TARGET VAR)
    if (CMAKE_COMPILER_IS_GNUCXX)
        set(COMPILER_FLAGS "")

        # Get flags form add_definitions, re-escape quotes
        get_target_property(TARGET_DEFS ${TARGET} COMPILE_DEFINITIONS)
        get_directory_property(DIRECTORY_DEFS COMPILE_DEFINITIONS)
        foreach (DEF ${TARGET_DEFS} ${DIRECTORY_DEFS})
            if (DEF)
                string(REPLACE "\"" "\\\"" DEF "${DEF}")
                list(APPEND COMPILER_FLAGS "-D${DEF}")
            endif ()
        endforeach ()

        # Get flags form include_directories()
        get_target_property(TARGET_INCLUDEDIRS ${TARGET} INCLUDE_DIRECTORIES)
        foreach (DIR ${TARGET_INCLUDEDIRS})
            if (DIR)
                list(APPEND COMPILER_FLAGS "-I${DIR}")
            endif ()
        endforeach ()

        # Get build-type specific flags
        string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_SUFFIX)
        separate_arguments(GLOBAL_FLAGS UNIX_COMMAND
                "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_TYPE_SUFFIX}}")
        list(APPEND COMPILER_FLAGS ${GLOBAL_FLAGS})

        # Add -std= flag if appropriate
        get_target_property(STANDARD ${TARGET} CXX_STANDARD)
        if ((NOT "${STANDARD}" STREQUAL NOTFOUND) AND (NOT "${STANDARD}" STREQUAL ""))
            list(APPEND COMPILER_FLAGS "-std=gnu++${STANDARD}")
        endif ()
    endif ()
    set(${VAR} "${COMPILER_FLAGS}")
endmacro ()

Ini dapat diperluas untuk mencakup opsi yang disebabkan oleh add_compiler_options() dan lebih banyak lagi.

person Fabian Knorr    schedule 05.03.2016
comment
Ini tidak berfungsi jika beberapa definisi diwarisi secara transitif dari perpustakaan tertaut - person random; 31.07.2018

Cara termudah adalah dengan menggunakan make VERBOSE=1 saat kompilasi.

cd my-build-dir
cmake path-to-my-sources
make VERBOSE=1

Ini akan melakukan build single-threaded, dan make akan mencetak setiap perintah shell yang dijalankannya tepat sebelum menjalankannya. Jadi Anda akan melihat output seperti:

[  0%] Building CXX object Whatever.cpp.o
<huge scary build command it used to build Whatever.cpp>
person Chris Kitching    schedule 05.03.2016
comment
Itu tentu berguna, tetapi saya perlu menggunakan kembali flag kompiler di proyek yang sama, jadi saya perlu memasukkannya ke dalam variabel. - person Fabian Knorr; 05.03.2016
comment
Itu lebih rumit. Saya yakin satu-satunya pilihan Anda adalah menggabungkan semua sumber bendera menjadi satu. Header yang telah dikompilasi sebelumnya merupakan sumber kerumitan yang signifikan dengan CMake. Banyak tanda juga memiliki kebalikannya yang akan menggantikan versi positif sebelumnya (jadi jika Anda hanya perlu mematikan beberapa hal, Anda dapat membaliknya dengan menambahkan tanda tambahan). Saya kira Anda pernah melihat ini: stackoverflow. com/questions/148570/ - person Chris Kitching; 05.03.2016

Sebenarnya ada cara yang cukup bersih untuk melakukan ini pada waktu kompilasi menggunakan CXX_COMPILER_LAUNCHER:

Jika Anda memiliki skrip print_args.py

#!/usr/bin/env python
import sys
import argparse

print(" ".join(sys.argv[1:]))
# we need to produce an output file so that the link step does not fail
p = argparse.ArgumentParser()
p.add_argument("-o")
args, _ = p.parse_known_args()
with open(args.o, "w") as f:
    f.write("")

Anda dapat mengatur properti target sebagai berikut:

add_library(${TARGET_NAME} ${SOURCES})
set_target_properties(${TARGET_NAME} PROPERTIES
      CXX_COMPILER_LAUNCHER
        ${CMAKE_CURRENT_SOURCE_DIR}/print_args.py
)
# this tells the linker to not actually link. Which would fail because output file is empty
set_target_properties(${TARGET_NAME} PROPERTIES
      LINK_FLAGS
        -E
)

Ini akan mencetak perintah kompilasi yang tepat pada waktu kompilasi.

person Jan15    schedule 13.12.2019

Jawaban singkat

Tidak mungkin untuk menetapkan nilai akhir baris perintah kompiler ke variabel dalam skrip CMake, yang berfungsi di semua kasus penggunaan.

Jawaban panjang

Sayangnya, bahkan solusi yang diterima sebagai jawaban masih belum mendapatkan semua tanda kompiler. Seperti yang disebutkan di komentar, ada Persyaratan Penggunaan Transitif. Ini adalah cara modern dan tepat untuk menulis file CMake, yang semakin populer. Anda juga mungkin memiliki beberapa opsi kompilasi yang ditentukan menggunakan ekspresi generator (terlihat seperti referensi variabel tetapi tidak akan diperluas bila diperlukan).

Pertimbangkan untuk memiliki contoh berikut:

add_executable(myexe ...);
target_compile_definitions(myexe PRIVATE "PLATFORM_$<PLATFORM_ID>");
add_library(mylib ...);
target_compile_definitions(mylib INTERFACE USING_MY_LIB);
target_link_libraries(myexe PUBLIC mylib);

Jika Anda mencoba memanggil makro GET_COMPILER_FLAGS yang diusulkan dengan target myexe, Anda akan mendapatkan keluaran -DPLATFORM_$<PLATFORM_ID>, bukan -DPLATFORM_Linux -DUSING_MY_LIB yang diharapkan.

Hal ini karena ada dua tahapan antara menjalankan CMake dan menghasilkan sistem build:

  1. Pengolahan. Pada tahap ini CMake membaca dan mengeksekusi perintah dari skrip cmake, khususnya, nilai variabel yang dievaluasi dan ditetapkan. Saat ini CMake hanya mengumpulkan semua informasi yang diperlukan dan bersiap untuk menghasilkan sistem build (makefiles).
  2. Menghasilkan. CMake menggunakan nilai variabel khusus dan properti, ditinggalkan di akhir skrip yang diproses untuk akhirnya memutuskan dan bentuk keluaran yang dihasilkan. Di sinilah ia membangun baris perintah akhir untuk kompiler sesuai dengan algoritma internalnya, tidak tersedia untuk skrip.

Properti target yang mungkin diambil pada tahap pemrosesan dengan get_target_property(...) atau get_property(... TARGET ...) tidak lengkap (bahkan ketika dipanggil di akhir skrip). Pada tahap pembuatan, CMake menelusuri setiap pohon ketergantungan target (secara rekursif) dan menambahkan nilai properti sesuai dengan persyaratan penggunaan transitif (nilai yang diberi tag PUBLIC dan INTERFACE disebarkan).

Meskipun demikian, ada solusinya, tergantung pada hasil akhir yang ingin Anda capai. Hal ini dimungkinkan dengan menerapkan ekspresi generator, yang memungkinkan penggunaan nilai final dari properti target apa pun (ditentukan pada tahap pemrosesan)... tetapi nanti!

Tersedia dua kemungkinan umum:

  1. Hasilkan file keluaran apa pun berdasarkan templat, yang isinya berisi referensi variabel dan/atau ekspresi generator, dan didefinisikan sebagai nilai variabel string, atau file masukan. Ini tidak fleksibel karena dukungan logika kondisional yang sangat terbatas (yaitu Anda tidak dapat menggunakan rangkaian kompleks yang hanya tersedia dengan loop foreach() bersarang), tetapi memiliki kelebihan, yaitu tidak diperlukan tindakan lebih lanjut dan konten dijelaskan dengan cara yang tidak bergantung pada platform. Gunakan varian perintah file(GENERATE ...). Perhatikan, perilakunya berbeda dari varian file (WRITE ...).
  2. Tambahkan target khusus (dan/atau perintah khusus) yang mengimplementasikan penggunaan lebih lanjut dari nilai yang diperluas. Ini bergantung pada platform dan mengharuskan pengguna untuk memanggil make tambahan (baik dengan beberapa target khusus, atau menyertakan ke target all), tetapi memiliki keuntungan, yaitu cukup fleksibel karena Anda dapat mengimplementasikan skrip shell (tetapi tanpa bit yang dapat dieksekusi).

Contoh yang menunjukkan solusi dengan menggabungkan opsi-opsi ini:

set(target_name "myexe")
file(GENERATE OUTPUT script.sh CONTENT "#!/bin/sh\n echo \"${target_name} compile definitions: $<TARGET_PROPERTY:${target_name},COMPILE_DEFINITIONS>\"")
add_custom_target(mycustomtarget
  COMMAND echo "\"Platform: $<PLATFORM_ID>\""
  COMMAND /bin/sh -s < script.sh
  )

Setelah memanggil direktori build CMake akan berisi file script.sh dan memanggil make mycustomtarget akan mencetak ke konsol:

Platform: Linux
myexe compile definitions: PLATFORM_Linux USING_MY_LIB
person Artem Pisarenko    schedule 05.11.2019