关于cmake和开源项目发布的那些事(PF)

 

  本来是打算写一篇年终总结,随便和以往一样提一提自己的开源项目(长不大的plain framework)的一些进度,不过最近这一年对于这个项目实在是维护不多,实在难以用它作为醒目的标题。而最近由于使用了VS2022,微软居然自动识别了项目中的cmake(看来我是很久没有使用这个工具了),于是在想方设法将这个项目做到可以在windows平台上尽快提供编译支持,其中遇到了许多有关的技术问题,我觉得可以在这里为大家提供一定的借鉴,特别是自己想要拥有快速编写项目的技巧。分享虽然微不足道,但是也希望大家在此能够有所收获。

  2022的新春就要到了,新的一年(手动狗头,这是指旧历),祝福大家能够平安喜乐!

1、项目地址

   https://github.com/viticm/plain

 

关于cmake和开源项目发布的那些事(PF)

 

  每次将地址放出来,感觉像是为自己的孩子做宣传,真的是可怜天下父母心。虽然这个孩子看起来实在太平庸了,可是我想说的是它还是有一定潜力的,至少在大多数的网络应用中都能够很好地发挥其作用。核心的框架并没有过多依赖,只需要依赖于标准的C/C++库即可,目前支持的语法为C++11。

  核心的模块:基础(basic)、网络(net)、文件(file)、系统(system)、数据库(database)、脚本(script)

  具体的我不再这里描述了,我之前对这个项目写过一些较为详细的介绍(估计也不够详细大家将就看吧)。

 

2、windows下的cmake

  接下来开始上主菜,一切都源于这张图:

关于cmake和开源项目发布的那些事(PF)

   如果没有更改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结构如下:

关于cmake和开源项目发布的那些事(PF)

  如图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进行编译(是一个动图)

关于cmake和开源项目发布的那些事(PF)

  运行测试(这个测试是自从编写db模块时才加入的,因此不会太多,在后续大版本中会坚持每一个接口增加):

 关于cmake和开源项目发布的那些事(PF)

   关于测试遇到的问题

  我这里要说的这个问题是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了,如下面动图的开始那样(是不是很简单?)。

关于cmake和开源项目发布的那些事(PF)

 

3、1.1.0

  在我编写这篇有关发布文章的时候,其实自己也在准备PF第一个版本的发布,以前没有正经的做过发布,这次发布出来是为了能够同大家一起研究和学习,不足之处还请指正。开源项目位于github,不过这个网站这两年很不稳定,还希望大家多一点耐心等待,要么就是用一下科学的工具吧。

  plain项目(提供了框架库和简单的示例)

关于cmake和开源项目发布的那些事(PF)关于cmake和开源项目发布的那些事(PF)

  plain-simple(框架稍微详细的示例,里面包含了一个目前上线应用的例子)

关于cmake和开源项目发布的那些事(PF)

 

  写在最后

  在这里再次祝福大家新年快乐,希望所有困扰我们的通通都消散,希望全世界和平美好!

  如果有需要可以加入我们的QQ群(348477824),这是一个潜水专用群,群主基本上已经是潜水几年了,但是如果你需要进行技术交流,那么可以到群里来闲聊。

上一篇:在 CMake 项目中使用 protobuf


下一篇:cmake例程学习