Недавно я немного поэкспериментировал с cmake и наткнулся на определение project, где вы можете указать имя, версию, описание и языки.

Затем я узнал, что вы можете передать эту информацию в код для печати, например, при запуске программы, со следующими строками:

add_compile_definitions( PROJECT_NAME="${PROJECT_NAME}" )
add_compile_definitions( PROJECT_VERSION="${PROJECT_VERSION}" )
add_compile_definitions( PROJECT_DESCRIPTION="${PROJECT_DESCRIPTION}" )

Мне было интересно, как я могу расширить это, чтобы включить имя (имена) автора и / или информацию о лицензии. Я начал со следующего:

set (
    PROJECT_AUTHORS
    fname1 lname1
    fname2 lname2
)
add_compile_definitions( PROJECT_AUTHORS="${PROJECT_AUTHORS}" )

Это привело к:

$ make src/main.i
Preprocessing C source to CMakeFiles/cmake_test.dir/src/main.c.i
/usr/bin/sh: -c: line 0: unexpected EOF while looking for matching `"'
/usr/bin/sh: -c: line 1: syntax error: unexpected end of file
make[1]: *** [CMakeFiles/cmake_test.dir/build.make:87: CMakeFiles/cmake_test.dir/src/main.c.i] Error 1
make: *** [Makefile:187: src/main.c.i] Error 2

Если я затем изменю строку определения на add_compile_definitions( PROJECT_AUTHORS=${PROJECT_AUTHORS} ) (убрал " вокруг переменной). В результате получается просто fname1 в коде (как обычный текст, а не строка) ...

Затем я изменил CMakeLists.txt на:

set (
    PROJECT_AUTHORS
    "fname1 lname1"
    "fname2 lname2"
)
add_compile_definitions( PROJECT_AUTHORS="${PROJECT_AUTHORS}" )

В результате:

$ make src/main.i
Preprocessing C source to CMakeFiles/cmake_test.dir/src/main.c.i
/usr/bin/sh: -c: line 0: unexpected EOF while looking for matching `"'
/usr/bin/sh: -c: line 1: syntax error: unexpected end of file
make[1]: *** [CMakeFiles/cmake_test.dir/build.make:87: CMakeFiles/cmake_test.dir/src/main.c.i] Error 1
make: *** [Makefile:187: src/main.c.i] Error 2

Есть идеи или предложения о том, как я могу это сделать?

Для информации:

  • cmake --version -> cmake version 3.19.2
  • gcc --version -> gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
  • bash --version -> GNU bash, version 4.4.23(1)-release (x86_64-pc-msys)
0
Pieter-Jan Cassiman 12 Июн 2021 в 20:22

2 ответа

Лучший ответ

Я думаю, что простой способ - использовать configure_file. Создайте файл:

# authors.h.in
#cmakedefine PROJECT_AUTHORS "@PROJECT_AUTHORS@"

Затем:

$ cat CMakeLists.txt 
cmake_minimum_required(VERSION 3.11)
project(test)
set(PROJECT_AUTHORS "fname1 lname1" "fname2 lname2")
configure_file(authors.h.in ${CMAKE_CURRENT_BINARY_DIR}/gen/authors.h)
add_executable(main main.c)
target_include_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/gen)
include(CTest)
add_test(NAME main COMMAND main)

А также:

#include <authors.h>
#include <stdio.h>
#include <string.h>
int main() {
    char authors[] = PROJECT_AUTHORS;
    for (char *author = strtok(authors, ";"); author; author = strtok(0, ";")) {
        printf("%s\n", author);
    }
}

И после компиляции:

$ build_dir/main
fname1 lname1
fname2 lname2

Проблема заключается в символе ;, который возникает в результате объединения переменной списка в cmake в строку. В то время как ; - это разделитель аргументов в CMake. Вы можете заменить ;, например, на \; и, конечно, передать значение, которое является строковым литералом C. Например:

cmake_minimum_required(VERSION 3.11)
project(test)
set(PROJECT_AUTHORS "fname1 lname1" "fname2 lname2")
# TODO: I think also escape "" or for example newline
# ie preprocess the value so that all is nicely parsed
string(REGEX REPLACE ";" "\\\\;" val "${PROJECT_AUTHORS}")
add_executable(main main.c)
target_compile_definitions(main PRIVATE "PROJECT_AUTHORS=\"${val}\"")
include(CTest)
add_test(NAME main COMMAND main)

С тем же main.c, кроме #include <authors.h>, и он также отлично работает:

$ cmake -S . -B _build -G Ninja && cmake --build _build --verbose && ( cd _build && ctest -V )
...
[1/2] /usr/bin/cc -DPROJECT_AUTHORS="\"fname1 lname1;fname2 lname2\""   -MD -MT CMakeFiles/main.dir/main.c.o -MF CMakeFiles/main.dir/main.c.o.d -o 
....
1: Test command: /tmp/10/_build/main
1: Test timeout computed to be: 1500
1: fname1 lname1
1: fname2 lname2
1/1 Test #1: main .............................   Passed    0.00 sec
0
KamilCuk 12 Июн 2021 в 18:29

Обратите внимание, что определения компиляции в основном работают как препроцессоры #define; вам нужно убедиться, что компилятор сможет разумно использовать значение valus. Обратите внимание на использование переменной списка, например

set (
    PROJECT_AUTHORS
    "fname1 lname1"
    "fname2 lname2"
)

В строке приведет к разделению элементов точкой с запятой; PROJECT_AUTHORS="${PROJECT_AUTHORS}" имеет тот же эффект, что и PROJECT_AUTHORS="fname1 lname1;fname2 lname2", и вы, вероятно, не сможете использовать fname1 lname1;fname2 lname2 где-нибудь в своем коде.

Если вы хотите использовать строку, вам нужно сделать кавычки частью определения компиляции.

target_compile_definitions(myTarget PRIVATE "PROJECT_AUTHORS=\"${PROJECT_AUTHORS}\"")

В зависимости от ваших требований вы можете вместо этого сгенерировать код и записать его в исходный файл:

set(PROJECT_INFO
"
#include <vector>
#include <string>

std::vector<std::string> GetProjectAuthors()
{
    return {"
)

foreach(_AUTHOR IN LISTS PROJECT_AUTHORS)
    set(PROJECT_INFO "${PROJECT_INFO}
        \"${_AUTHOR}\",")
endforeach()

set(PROJECT_INFO "${PROJECT_INFO}
    };
}
")

file(WRITE ${PROJECT_INFO_FILE} ${PROJECT_INFO})
0
fabian 12 Июн 2021 в 18:23