รับบรรทัดคำสั่งคอมไพเลอร์ C++ แบบเต็ม

ใน CMake แฟล็กสำหรับคอมไพลเลอร์ C++ สามารถได้รับผลกระทบได้หลายวิธี: การตั้งค่า CMAKE_CXX_FLAGS ด้วยตนเอง การใช้ add_definitions() การบังคับใช้มาตรฐาน C++ บางอย่าง เป็นต้น

ในการคอมไพล์เป้าหมายในโปรเจ็กต์เดียวกันที่มีกฎต่างกัน (ในกรณีของฉันคือส่วนหัวที่คอมไพล์แล้ว) ฉันจำเป็นต้องทำซ้ำคำสั่งที่ใช้ในการคอมไพล์ไฟล์ที่เพิ่มโดยคำสั่งเช่น add_executable() ในไดเร็กทอรีนี้

การอ่าน CMAKE_CXX_FLAGS จะส่งคืนเฉพาะค่าที่ตั้งไว้อย่างชัดเจน CMAKE_CXX_FLAGS_DEBUG และรายการพี่น้องจะแสดงเฉพาะตัวเลือกดีบัก/รีลีสเริ่มต้นเท่านั้น มีฟังก์ชันพิเศษในการดึงค่าสถานะจาก add_definitions() และ add_compiler_options() แต่ดูเหมือนจะไม่มีใครสามารถส่งคืนบรรทัดคำสั่งสุดท้ายได้

ฉันจะทำให้แฟล็กทั้งหมดส่งผ่านไปยังคอมไพเลอร์ไปยังตัวแปร CMake ได้อย่างไร


person Fabian Knorr    schedule 04.03.2016    source แหล่งที่มา


คำตอบ (4)


เพื่อตอบคำถามของฉันเอง: ดูเหมือนว่าวิธีเดียวที่จะได้รับแฟล็กคอมไพเลอร์ทั้งหมดคือการสร้างแฟล็กใหม่จากแหล่งต่างๆ รหัสที่ฉันทำงานอยู่ตอนนี้มีดังต่อไปนี้ (สำหรับ 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 ()

สิ่งนี้สามารถขยายเพื่อรวมตัวเลือกที่เกิดจาก add_compiler_options() และอื่นๆ ได้ด้วย

person Fabian Knorr    schedule 05.03.2016
comment
สิ่งนี้ใช้ไม่ได้หากคำจำกัดความบางอย่างสืบทอดมาจากไลบรารีที่เชื่อมโยง - person random; 31.07.2018

วิธีที่ง่ายที่สุดคือใช้ make VERBOSE=1 เมื่อคอมไพล์

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

สิ่งนี้จะสร้างบิลด์แบบเธรดเดียว และ make จะพิมพ์ทุกคำสั่งเชลล์ที่มันรันก่อนที่จะรัน ดังนั้นคุณจะเห็นผลลัพธ์ดังนี้:

[  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
นั่นมีประโยชน์อย่างแน่นอน แต่ฉันจำเป็นต้องใช้แฟล็กคอมไพเลอร์ในโปรเจ็กต์เดียวกันอีกครั้ง ดังนั้นฉันจึงจำเป็นต้องนำแฟล็กเหล่านั้นไปไว้ในตัวแปร - person Fabian Knorr; 05.03.2016
comment
นั่นเป็นเรื่องที่ยุ่งยากกว่า ฉันเชื่อว่าทางเลือกเดียวของคุณคือเชื่อมโยงแหล่งที่มาของแฟล็กต่างๆ ทั้งหมดเข้าด้วยกัน ส่วนหัวที่คอมไพล์แล้วเป็นแหล่งของความยุ่งยากที่สำคัญกับ CMake ธงจำนวนมากยังมีสิ่งที่ตรงกันข้ามที่จะแทนที่เวอร์ชันบวกก่อนหน้านี้ (ดังนั้นหากคุณเพียงต้องการปิดบางสิ่ง ก็สามารถพลิกได้โดย เพิ่ม ธงพิเศษ) ฉันเดาว่าคุณคงเคยเห็นสิ่งนี้: stackoverflow com/questions/148570/ - person Chris Kitching; 05.03.2016

จริงๆ แล้วมีวิธีที่ค่อนข้างสะอาดในการดำเนินการนี้ในเวลารวบรวมโดยใช้ CXX_COMPILER_LAUNCHER:

หากคุณมีสคริปต์ 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("")

คุณสามารถตั้งค่าคุณสมบัติของเป้าหมายได้ดังนี้:

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
)

สิ่งนี้จะพิมพ์คำสั่งการคอมไพล์ที่แน่นอนในเวลาคอมไพล์

person Jan15    schedule 13.12.2019

คำตอบสั้น ๆ

ไม่สามารถกำหนดค่าสุดท้ายของบรรทัดคำสั่งคอมไพเลอร์ให้กับตัวแปรในสคริปต์ CMake ได้ ซึ่งทำงานได้ในทุกกรณีการใช้งาน

ตอบยาว

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

ลองพิจารณาตัวอย่างต่อไปนี้:

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

หากคุณพยายามเรียกแมโคร GET_COMPILER_FLAGS ที่เสนอด้วยเป้าหมาย myexe คุณจะได้รับผลลัพธ์ผลลัพธ์ -DPLATFORM_$<PLATFORM_ID> แทนที่จะเป็น -DPLATFORM_Linux -DUSING_MY_LIB ที่คาดไว้

เนื่องจากมีสองขั้นตอนระหว่างการเรียกใช้ CMake และการสร้างระบบ build:

  1. กำลังประมวลผล. ในขั้นตอนนี้ CMake อ่านและดำเนินการคำสั่งจากสคริปต์ cmake โดยเฉพาะค่าตัวแปรที่ได้รับการประเมินและกำหนด ในขณะนี้ CMake เพิ่งรวบรวมข้อมูลที่จำเป็นทั้งหมดและเตรียมสร้างระบบบิลด์ (makefiles)
  2. กำลังสร้าง CMake ใช้ค่าของ ตัวแปรพิเศษ และ คุณสมบัติ ถูกทิ้งไว้ที่ส่วนท้ายของสคริปต์ที่ประมวลผลเพื่อตัดสินใจและสุดท้าย แบบฟอร์มที่สร้างเอาต์พุต นี่คือที่ที่มันสร้างบรรทัดคำสั่งสุดท้ายสำหรับคอมไพเลอร์ตามอัลกอริธึมภายใน ซึ่งไม่สามารถใช้กับการเขียนสคริปต์ได้

คุณสมบัติเป้าหมายซึ่งอาจดึงข้อมูลได้ในขั้นตอนการประมวลผลด้วย get_target_property(...) หรือ get_property(... TARGET ...) ไม่สมบูรณ์ (แม้ว่าจะเรียกใช้ที่ส่วนท้ายของสคริปต์ก็ตาม) ในขั้นตอนการสร้าง CMake จะเดินผ่านแผนผังการพึ่งพาเป้าหมายแต่ละอัน (แบบเรียกซ้ำ) และผนวกค่าคุณสมบัติตามความต้องการการใช้งานแบบสกรรมกริยา (ค่าที่ติดแท็ก PUBLIC และ INTERFACE ได้รับการเผยแพร่)

แม้ว่าจะมีวิธีแก้ปัญหา ขึ้นอยู่กับผลลัพธ์สุดท้ายที่คุณตั้งเป้าไว้ ซึ่งสามารถทำได้โดยการใช้ นิพจน์ตัวสร้าง ซึ่งอนุญาตให้ใช้ค่า สุดท้าย ของคุณสมบัติของเป้าหมายใด ๆ (กำหนดไว้ในขั้นตอนการประมวลผล)... แต่ในภายหลัง!

ความเป็นไปได้ทั่วไปสองประการสามารถใช้ได้:

  1. สร้างไฟล์เอาต์พุตใดๆ ตามเทมเพลต ซึ่งเนื้อหามีการอ้างอิงตัวแปรและ/หรือนิพจน์ตัวสร้าง และกำหนดเป็นค่าตัวแปรสตริงหรือไฟล์อินพุต ไม่ยืดหยุ่นเนื่องจากการรองรับตรรกะแบบมีเงื่อนไขที่จำกัดมาก (เช่น คุณไม่สามารถใช้การต่อข้อมูลที่ซับซ้อนที่มีเฉพาะกับลูป foreach() ที่ซ้อนกันได้) แต่มีข้อดีคือ ไม่ต้องดำเนินการใดๆ เพิ่มเติม และเนื้อหาที่อธิบายด้วยวิธีที่ไม่ขึ้นกับแพลตฟอร์ม ใช้รูปแบบคำสั่ง file(GENERATE ...) โปรดทราบว่ามันทำงานแตกต่างจากตัวแปร file (WRITE ...)
  2. เพิ่มเป้าหมายที่กำหนดเอง (และ/หรือคำสั่งที่กำหนดเอง) ซึ่งใช้การใช้งานเพิ่มเติมของค่าที่ขยายเพิ่มเติม ขึ้นอยู่กับแพลตฟอร์มและต้องการให้ผู้ใช้เรียกใช้ make เพิ่มเติม (ไม่ว่าจะมีเป้าหมายพิเศษหรือรวมเข้ากับเป้าหมาย all) แต่มีข้อดีตรงที่มีความยืดหยุ่นเพียงพอเนื่องจากคุณสามารถใช้เชลล์สคริปต์ได้ (แต่ไม่มีบิตที่ปฏิบัติการได้)

ตัวอย่างที่สาธิตวิธีแก้ปัญหาด้วยการรวมตัวเลือกเหล่านี้:

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
  )

หลังจากการเรียกไดเร็กทอรีบิลด์ CMake จะมีไฟล์ script.sh และการเรียกใช้ make mycustomtarget จะพิมพ์ไปที่คอนโซล:

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