關於cmake和開源專案釋出的那些事(PF)

戀月發表於2022-01-21

 

  本來是打算寫一篇年終總結,隨便和以往一樣提一提自己的開源專案(長不大的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),這是一個潛水專用群,群主基本上已經是潛水幾年了,但是如果你需要進行技術交流,那麼可以到群裡來閒聊。

相關文章