关于cmake和开源项目发布的那些事(PF)
本来是打算写一篇年终总结,随便和以往一样提一提自己的开源项目(长不大的plain framework)的一些进度,不过最近这一年对于这个项目实在是维护不多,实在难以用它作为醒目的标题。而最近由于使用了VS2022,微软居然自动识别了项目中的cmake(看来我是很久没有使用这个工具了),于是在想方设法将这个项目做到可以在windows平台上尽快提供编译支持,其中遇到了许多有关的技术问题,我觉得可以在这里为大家提供一定的借鉴,特别是自己想要拥有快速编写项目的技巧。分享虽然微不足道,但是也希望大家在此能够有所收获。
2022的新春就要到了,新的一年(手动狗头,这是指旧历),祝福大家能够平安喜乐!
1、项目地址
https://github.com/viticm/plain
每次将地址放出来,感觉像是为自己的孩子做宣传,真的是可怜天下父母心。虽然这个孩子看起来实在太平庸了,可是我想说的是它还是有一定潜力的,至少在大多数的网络应用中都能够很好地发挥其作用。核心的框架并没有过多依赖,只需要依赖于标准的C/C++库即可,目前支持的语法为C++11。
核心的模块:基础(basic)、网络(net)、文件(file)、系统(system)、数据库(database)、脚本(script)
具体的我不再这里描述了,我之前对这个项目写过一些较为详细的介绍(估计也不够详细大家将就看吧)。
2、windows下的cmake
接下来开始上主菜,一切都源于这张图:
如果没有更改VS中默认的设置,那么它在打开文件夹时会自动识别目录下的CMakelist.txt,然后你就会发现这个页面了。它的目的是为提醒我们进行cmake相关的设置,有点像是游戏里面的引导功能,在IDE里微软的VS还是很注重用户体验的。虽然它出现了这个页面,但在跨平台开发的时候我仍然习惯于直接到相应的系统下直接开发,者或许是因为还没有真正体验到一个IDE跨平台开发的乐趣吧。但为了更好的开发编译,最近半个月时间几乎对于项目的维护都在了CMake这里,可以看到提交最多的注释为Update cmake。
plain下面的CMakelist(根目录cmake/CMakelist.txt)
# Copyright 2017 Viticm. All rights reserved. # # Licensed under the MIT License(the "License"); # you may not use this file except in compliance with the License. # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 2.8.12) set(PROJECT_NAME PlainFramework) set(PF_VERSION 1.1.0) set(PROJECT_DESC "Plain framework, based on c++ for net applictions") if (CMAKE_VERSION VERSION_LESS 3.0) project(PlainFramework CXX C) else() cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0037 NEW) project(PlainFramework VERSION ${PF_VERSION} LANGUAGES CXX C) endif() # Call fplutil to get locations of dependencies and set common build settings. include("inc/find_fplutil.cmake") include("inc/common.cmake") include("inc/internal_utils.cmake") if (NOT dependencies_gtest_dir) set(dependencies_gtest_dir ${root_dir}/dependencies/googletest/googletest) endif() if (NOT has_output_path) # This is the directory into which the executables are built. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${root_dir}/plain/bin) # This is the directory into which the librarys are built. set(LIBRARY_OUTPUT_PATH ${root_dir}/plain/lib) set(has_output_path 1) endif() #For utf8 no boom. if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4819") endif() # Options that control the build configuration. # To configure PlainFramework flags per build target, see the # plainframework_configure_flags() function. option(plainframework_build_tests "Build PlainFramework unit tests." ON) # Build plain framework plugins. option(plainframework_build_plugins "Build PlainFramework plugins" ON) file(GLOB_RECURSE PLAINFRAMEWORK_HEADERS ${CMAKE_CURRENT_LIST_DIR}/framework/core/include *.h) set(VERSION_RC ${root_dir}/cmake/inc/version.rc.in) add_subdir(${plainframework_dir}/cmake plainframework plainframework) # Plugins. if (plainframework_build_plugins) add_subdir(${root_dir}/plain/plugins/cmake plugins plainframework) endif() if(plainframework_build_tests) add_subdir(${root_dir}/framework/unit_tests/cmake ${root_dir}/framework/unit_tests/cmake/build plainframework) if (NOT plainframework_no_app) add_subdir(${root_dir}/plain/app/cmake ${root_dir}/plain/app/cmake/build plainframework) endif() endif()
root_dir(根目录)
这个变量是当前项目的绝对路径,在PF项目中这个绝对路径是相对于CMakelist而言,也就是在子项目所在的根目录,这样是为了每个项目设置可以独立进行设置。
这个变量在inc/common.cmake中,每个项目都这样设置:
set(root_dir ${CMAKE_CURRENT_LIST_DIR}/../.. CACHE INTERNAL "plainframework root directory")
设置的路径为inc目录的上两级目录,PF项目中的cmake结构如下:
如图inc的上两级目录就是plain,这样就获取到了项目所在的根目录,但这样的设置因人而异,或许大家能够想到更好的方式。
让VS编译的时候不提示编码的警告(由于项目大胆的使用了google,因此整体的警告等级为最高4,而且所有警告都视为错误)
作为纯粹的开发者,no boom的utf8文件才是可选的,由于历史原因微软各种自己使用的utf8文件都是加上了boom标记。
#For utf8 no boom. if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4819") endif()
has_output_path(是否指定输出目录)
这个变量的目的为控制每个项目的输出路径,在VS中有生成后事件,也可以将生成的文件拷贝到自己想要的目录,但我自认为不太方便,直接就编译到指定目录才是王道。
设置运行文件生成目录
# This is the directory into which the executables are built. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${root_dir}/plain/bin)
在windows下这个目录输出工程的exe和dll等文件,在linux下输出的是可执行文件和so动态库。
设置库文件生成目录
# This is the directory into which the librarys are built. set(LIBRARY_OUTPUT_PATH ${root_dir}/plain/lib)
在windows下这个目录输出工程的lib和exp等文件,在linux下输出的是.a文件。
add_dir(添加目录)
在PF中为了保证每个子目录或者项目的根目录被正确设置,因此自己封装了这个添加目录的函数用以替换直接使用add_subdirectory。
# Safe add_subdirectory. function(add_subdir target target_build project) set_compiler_flags_for_external_libraries() set(saved_root_dir${project} ${root_dir} CACHE INTERNAL "root dir cache") add_subdirectory(${target} ${target_build}) set(root_dir ${saved_root_dir${project}} CACHE INTERNAL "root dir recover") restore_compiler_flags() endfunction(add_subdir)
其目的保证当前的root_dir在子目录添加后不被更改,保证当前的编译变量在添加之后和之前一样(这里或许有些问题),个人认为这样暂时足够使用而且还挺方便的。
下面的命令即是添加框架的所在目录:
add_subdir(${plainframework_dir}/cmake plainframework plainframework)
在windows上使用cmake进行编译(是一个动图)
运行测试(这个测试是自从编写db模块时才加入的,因此不会太多,在后续大版本中会坚持每一个接口增加):
关于测试遇到的问题
我这里要说的这个问题是windows上的,以前没有写测试用例的时候根本没有关注这个问题,其罪魁元首我先直接贴在最前面(internal_utils.cmake):
if (NOT BUILD_SHARED_LIBS AND NOT pf_force_shared_crt) # When Plain Framework is built as a shared library, it should also use # shared runtime libraries. Otherwise, it may end up with multiple # copies of runtime library data in different modules, resulting in # hard-to-find crashes. When it is built as a static library, it is # preferable to use CRT as static libraries, as we don't have to rely # on CRT DLLs being available. CMake always defaults to using shared # CRT libraries, so we override that default here. string(REPLACE "/MD" "-MT" ${flag_var} "${${flag_var}}") endif()
这段代码是谷歌的,我之前一直连接的时候都是使用谷歌的静态库,其实都是为了方便。作为第三方的gtest,我直接将它作为自己的子模块,而且不能修改的子模块,用静态库我就不用在生成的时候去特意拷贝到自己的运行目录了(windows)。可是最后发现,运行测试的时候直接产生了一个异常断点,提示的是acrt_first_block==header。说实话对windows开发还缺少经验的我来说,遇到这个问题第一时间只能搜索查找资料,但是你会发现与此相关的都是内存泄漏。但转念我想到过,对于内存问题,PF是经过一段优化的,因此还是心存怀疑,于是使用vs进行调试这次提示的是内存访问冲突。
最后让我怀疑是动态库的原因,是看到了一篇文章,这是无意发现的,这也许是经过了几天摸不着头脑,老天可怜的缘故吧。于是我仔细检查了所有的cmake编译脚本文件,很快就定位到了上述怀疑的地方。想不到当初为了偷懒,到头来却为自己带来了几天的麻烦,关于windows的内存分配可以搜索HeapAlloc关键字,里面有详细关于dll的内存分配。为了节省时间,加上本身不愿意再去做修改,因此加上了下面的编译脚本(当初只是为了不做这一步)。
# Copy gtest libraries. if (MSVC AND pf_build_shared AND BUILD_SHARED_LIBS) if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") add_custom_command(TARGET core_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/bin/gtestd.dll ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ) else() add_custom_command(TARGET core_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/bin/gtest.dll ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ) endif() endif()
上面的目的很简单,在不同的类型下拷贝不同的gtest动态库。
默认示例
如果你使用PF进行开发,那么可以先从这个简单的示例开始(这里有点跑题,不过目前在cmake中遇到的问题已经差不多讲完了,那么就说说相关的题外话)。
配置的细节就不用说了,新建一个cpp文件就可以快速开始使用PF了,如下面动图的开始那样(是不是很简单?)。
3、1.1.0
在我编写这篇有关发布文章的时候,其实自己也在准备PF第一个版本的发布,以前没有正经的做过发布,这次发布出来是为了能够同大家一起研究和学习,不足之处还请指正。开源项目位于github,不过这个网站这两年很不稳定,还希望大家多一点耐心等待,要么就是用一下科学的工具吧。
plain项目(提供了框架库和简单的示例)
plain-simple(框架稍微详细的示例,里面包含了一个目前上线应用的例子)
写在最后
在这里再次祝福大家新年快乐,希望所有困扰我们的通通都消散,希望全世界和平美好!
如果有需要可以加入我们的QQ群(348477824),这是一个潜水专用群,群主基本上已经是潜水几年了,但是如果你需要进行技术交流,那么可以到群里来闲聊。