В общем я недавно решил все же детальнее разобраться в cmake и для этого прошел обучающие материалы с официального сайта, после чего вроде как понял что за фрукт cmake и без нейронки могу теперь этим всем делом заниматься, решил автоматизировать давно наболевшую вещь, такую как: определение типа библиотеки по имеющимся файлам, и автоматическую их линковку к цели.
Что я имею ввиду:
Представим вы начинаете новый проект, или хотите проверить фишку, наверняка у вас есть какой-то шаблон проектов, если вы пишете на cmake или что-то типа этого. То есть у вас там есть создание исполняемого файла - add_executable(target main.cpp), вы довольные решаетесь чуть чуть увеличить проект и... после того как вы создали файлы, вы обязательно лезете менять свой CMakeLists.txt, добавляете новые цели, меняете тип цели, списки. Да можно использовать file(GLOB_RECURSE ...), но за типом цели тоже необходимо следить, а если таких списков несколько? сплошной лишний гемморой. Так вот, моя функция create_lib() призвана решить этот наболевший вопрос. Реализовал я ее через конечный автомат, это пожалуй лучшее до чего я смог додуматься. И вот на радостях написав сложную штуку я пошел хвастаться перед нейронкой своим большим умом и... она обосрала его. Типа да, я понимаю, слушать нейросеть это бредово и смешно, но да, у меня нет друзей, а айтишников, которые шарят за с++, за его глубокую экосистему точно нет. И этот пост, наверное, мне нужен чтобы восстановить увереность в себе что-ли...
Сам вопрос!
Обсирать код есть за что, но неужели я действительно сделал бред, и его можно было как-то упростить, сохранив гибкость и отличную расширяемость?
p.s. По любым вопросам обращайтесь, я готов ответить на них если что-то неясно, так же будет повод делать комментарии в коде к непонятным элементам
function(set_state currentItem state)
if (${${currentItem}} STREQUAL FILE_SET)
set(${state} SET_FILESET PARENT_SCOPE)
elseif (${${currentItem}} STREQUAL BASE_DIRS)
set(${state} COLLECTING_DIRS PARENT_SCOPE)
elseif (${${currentItem}} STREQUAL FILES)
set(${state} COLLECTING_FILES PARENT_SCOPE)
elseif (${${currentItem}} STREQUAL TYPE)
set(${state} SET_TYPE PARENT_SCOPE)
elseif (${${currentItem}} STREQUAL SOURCE_END)
set(${state} END_STATE PARENT_SCOPE)
elseif (${${currentItem}} STREQUAL LIB_TYPE)
set(${state} SKIP PARENT_SCOPE)
else()
if (${${currentItem}} STREQUAL PRIVATE OR
${${currentItem}} STREQUAL PUBLIC OR
${${currentItem}} STREQUAL INTERFACE)
set(${state} NEW_SET PARENT_SCOPE)
else()
set(${state} "" PARENT_SCOPE)
endif()
endif()
endfunction()
# Конечный автомат выбран, потому что:
# 1. Аргументы могут содержать перемешанные PRIVATE/PUBLIC/FILE_SET
# 2. Нужно пропускать пустые группы ресурсов без ошибки
# 3. BASE_DIRS может идти до или после FILES
# 4. HEADERS автоматически задаёт TYPE HEADERS
# 5. Нужно отличать интерфейсную библиотеку от обычной
function(is_interfaceSource SourceArray flag)
set(state COLLECTING_FILES)
list(APPEND ${SourceArray} SOURCE_END)
set(access PRIVATE)
set(file_set "")
set(set_type "")
set(base_dirs "")
set(files "")
foreach(item IN LISTS ${SourceArray})
set_state(item candidate_state)
if (candidate_state)
if (candidate_state STREQUAL NEW_SET OR candidate_state STREQUAL END_STATE)
if (files AND NOT file_set)
if (NOT base_dirs OR file_set OR set_type)
set(${flag} FALSE PARENT_SCOPE)
return()
endif()
endif()
set(access ${item})
set(state COLLECTING_FILES)
set(file_set "")
set(set_type "")
set(base_dirs "")
set(files "")
else()
set(state ${candidate_state})
endif()
continue()
endif()
if(state STREQUAL SET_FILESET)
set(file_set ${item})
if(${item} STREQUAL HEADERS)
set(set_type ${item})
endif()
elseif (state STREQUAL SET_TYPE)
set(set_type ${item})
elseif (state STREQUAL COLLECTING_DIRS)
list(APPEND base_dirs ${item})
elseif (state STREQUAL COLLECTING_FILES)
list(APPEND files ${item})
elseif (state)
continue()
endif()
endforeach()
set(${flag} TRUE PARENT_SCOPE)
endfunction()
function(target_source_setter custom_target SourceArray)
set(state COLLECTING_FILES)
list(APPEND ${SourceArray} SOURCE_END)
set(access PRIVATE)
set(file_set "")
set(set_type "")
set(base_dirs "")
set(files "")
foreach(item IN LISTS ${SourceArray})
set_state(item candidate_state)
if (candidate_state)
if (candidate_state STREQUAL NEW_SET OR candidate_state STREQUAL END_STATE)
if (file_set)
if (NOT base_dirs OR files OR file_set OR set_type)
target_sources(${custom_target}
${access}
FILE_SET ${file_set}
TYPE ${set_type}
BASE_DIRS ${base_dirs}
FILES ${files}
)
else()
message("[WARNING] The resource package is incomplete;")
endif()
elseif(files)
if (NOT "${base_dirs} ${file_set} ${set_type}")
target_sources(${custom_target}
${access}
${files}
)
else()
message("[WARNING] Attention, translation units must not have header set attribute")
endif()
endif()
set(access ${item})
set(state COLLECTING_FILES)
set(file_set "")
set(set_type "")
set(base_dirs "")
set(files "")
else()
set(state ${candidate_state})
endif()
continue()
endif()
if(state STREQUAL SET_FILESET)
set(file_set ${item})
if(${item} STREQUAL HEADERS)
set(set_type ${item})
endif()
elseif (state STREQUAL SET_TYPE)
set(set_type ${item})
elseif (state STREQUAL COLLECTING_DIRS)
list(APPEND base_dirs ${item})
elseif (state STREQUAL COLLECTING_FILES)
list(APPEND files ${item})
elseif (state)
continue()
endif()
endforeach()
set(${flag} TRUE PARENT_SCOPE)
endfunction()
# Создаёт библиотеку, автоматически определяя её тип.
# Позволяет не править CMakeLists.txt при добавлении/удалении файлов.
# Пустые списки пропускаются без ошибок.
function(create_lib libName)
set(ARG_LIST ${ARGN})
cmake_parse_arguments(arg "" "LIB_TYPE" "" ${ARGN})
is_interfaceSource(ARG_LIST flag)
if (flag)
add_library(${libName} INTERFACE)
message("[STATUS] This source is interface")
else()
add_library(${libName} ${arg_LIB_TYPE})
message("[STATUS] This source not a interface")
endif()
target_source_setter(${libName} ARG_LIST)
set(${libName} ${${libName}} PARENT_SCOPE)
endfunction()
Собственно сама функция. Я не знаю, может действительно есть решение проще и при этом такое же гибкое?
Если интересно, то вот пример ее использования в листе:
cmake_minimum_required(VERSION 3.25)
include(FetchContent)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibCreator.cmake)
project(HelloWorld)
set(PN ${PROJECT_NAME})
file(GLOB_RECURSE ${PN}_HEADERS
CONFIGURE_DEPENDS
"include/*.h"
)
file(GLOB_RECURSE ${PN}_SRC
CONFIGURE_DEPENDS
"src/*.cpp"
)
create_lib(${PN}_LIB
#LIB_TYPE STATIC
PUBLIC
FILE_SET HEADERS
BASE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/include
FILES
${${PN}_HEADERS}
PRIVATE
${${PN}_SRC}
)
add_executable(${PN} main.cpp)
target_link_libraries(${PN} PRIVATE ${PN}_LIB)
если какой-то список пустой, то он просто скипнется и не добавится в таргет, так что все работает без проблем и без файлов каких-либо.
Также интересно: реально ли я надумал себе поблему?