<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      CMake構(gòu)建學(xué)習(xí)筆記15-組建第一個(gè)程序項(xiàng)目

      1 概述

      在前文中論述的都是如何使用CMake構(gòu)建第三方依賴庫,不過這些庫都是別人的程序項(xiàng)目,那么如何使用CMake組織構(gòu)建一個(gè)屬于自己的C/C++程序項(xiàng)目呢?本文我們就來實(shí)現(xiàn)一個(gè)使用CMake組建的C/C++項(xiàng)目。

      2 具體案例

      2.1 代碼編寫

      就不去寫很簡(jiǎn)單的打印HelloWorld案例了,那種簡(jiǎn)單的案例實(shí)用的意義并不大,至少我們得使用調(diào)用一個(gè)第三方的依賴庫的例子。正好筆者寫過一個(gè)使用libzip壓縮文件和文件夾的例子,源代碼文件main.cpp如下所示:

      #include <zip.h>
      
      #include <filesystem>
      #include <fstream>
      #include <iostream>
      
      using namespace std;
      
      void CompressFile2Zip(std::filesystem::path unZipFilePath,
                            const char* relativeName, zip_t* zipArchive) {
        std::ifstream file(unZipFilePath, std::ios::binary);
        file.seekg(0, std::ios::end);
        size_t bufferSize = file.tellg();
        char* bufferData = (char*)malloc(bufferSize);
      
        file.seekg(0, std::ios::beg);
        file.read(bufferData, bufferSize);
      
        //第四個(gè)參數(shù)如果非0,會(huì)自動(dòng)托管申請(qǐng)的資源,直到zip_close之前自動(dòng)銷毀。
        zip_source_t* source =
            zip_source_buffer(zipArchive, bufferData, bufferSize, 1);
      
        if (source) {
          if (zip_file_add(zipArchive, relativeName, source, ZIP_FL_OVERWRITE) < 0) {
            std::cerr << "Failed to add file " << unZipFilePath
                      << " to zip: " << zip_strerror(zipArchive) << std::endl;
            zip_source_free(source);
          }
        } else {
          std::cerr << "Failed to create zip source for " << unZipFilePath << ": "
                    << zip_strerror(zipArchive) << std::endl;
        }
      }
      
      void CompressFile(std::filesystem::path unZipFilePath,
                        std::filesystem::path zipFilePath) {
        int errorCode = 0;
        zip_t* zipArchive = zip_open(zipFilePath.generic_u8string().c_str(),
                                     ZIP_CREATE | ZIP_TRUNCATE, &errorCode);
        if (zipArchive) {
          CompressFile2Zip(unZipFilePath, unZipFilePath.filename().string().c_str(),
                           zipArchive);
      
          errorCode = zip_close(zipArchive);
          if (errorCode != 0) {
            zip_error_t zipError;
            zip_error_init_with_code(&zipError, errorCode);
            std::cerr << zip_error_strerror(&zipError) << std::endl;
            zip_error_fini(&zipError);
          }
        } else {
          zip_error_t zipError;
          zip_error_init_with_code(&zipError, errorCode);
          std::cerr << "Failed to open output file " << zipFilePath << ": "
                    << zip_error_strerror(&zipError) << std::endl;
          zip_error_fini(&zipError);
        }
      }
      
      void CompressDirectory2Zip(std::filesystem::path rootDirectoryPath,
                                 std::filesystem::path directoryPath,
                                 zip_t* zipArchive) {
        if (rootDirectoryPath != directoryPath) {
          if (zip_dir_add(zipArchive,
                          std::filesystem::relative(directoryPath, rootDirectoryPath)
                              .generic_u8string()
                              .c_str(),
                          ZIP_FL_ENC_UTF_8) < 0) {
            std::cerr << "Failed to add directory " << directoryPath
                      << " to zip: " << zip_strerror(zipArchive) << std::endl;
          }
        }
      
        for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) {
          if (entry.is_regular_file()) {
            CompressFile2Zip(
                entry.path().generic_u8string(),
                std::filesystem::relative(entry.path(), rootDirectoryPath)
                    .generic_u8string()
                    .c_str(),
                zipArchive);
          } else if (entry.is_directory()) {
            CompressDirectory2Zip(rootDirectoryPath, entry.path().generic_u8string(),
                                  zipArchive);
          }
        }
      }
      
      void CompressDirectory(std::filesystem::path directoryPath,
                             std::filesystem::path zipFilePath) {
        int errorCode = 0;
        zip_t* zipArchive = zip_open(zipFilePath.generic_u8string().c_str(),
                                     ZIP_CREATE | ZIP_TRUNCATE, &errorCode);
        if (zipArchive) {
          CompressDirectory2Zip(directoryPath, directoryPath, zipArchive);
      
          errorCode = zip_close(zipArchive);
          if (errorCode != 0) {
            zip_error_t zipError;
            zip_error_init_with_code(&zipError, errorCode);
            std::cerr << zip_error_strerror(&zipError) << std::endl;
            zip_error_fini(&zipError);
          }
        } else {
          zip_error_t zipError;
          zip_error_init_with_code(&zipError, errorCode);
          std::cerr << "Failed to open output file " << zipFilePath << ": "
                    << zip_error_strerror(&zipError) << std::endl;
          zip_error_fini(&zipError);
        }
      }
      
      int main() {
        //壓縮文件
        //CompressFile("C:/Data/Builder/Demo/view.tmp", "C:/Data/Builder/Demo/view.zip");
      
        //壓縮文件夾
        CompressDirectory("C:/Data/Builder/Demo", "C:/Data/Builder/Demo.zip");
      
        return 0;
      }
      

      接下來就開始編寫CMake構(gòu)建系統(tǒng)的核心配置文件CMakeLists.txt。都說CMake的語法比較爛,但其實(shí)編寫一個(gè)CMakeLists.txt并算不太難。無論是在Windows下使用Microsoft Visual Studio創(chuàng)建MSVC工程,還是Linux下編寫Makefile文件,無非也是定義了項(xiàng)目的源代碼、庫依賴、編譯選項(xiàng)以及一些特別的構(gòu)建細(xì)節(jié),CMakeLists.txt中的內(nèi)容也是如此。只不過CMakeLists.txt中的一些寫法抹平的不同操作系統(tǒng)之間的差異,使得編譯器和鏈接器能夠相同的邏輯進(jìn)行工作。你可以這樣簡(jiǎn)單的理解,CMakeLists.txt是不同操作系統(tǒng)下不同構(gòu)建平臺(tái)定義的項(xiàng)目文件的再抽象,在進(jìn)行構(gòu)建工作的時(shí)候CMakeLists.txt會(huì)轉(zhuǎn)譯成相應(yīng)平臺(tái)下的程序項(xiàng)目。

      這里CMakeLists.txt的內(nèi)容如下所示:

      # 輸出cmake版本提示
      message(STATUS "The CMAKE_VERSION is ${CMAKE_VERSION}.")
      
      # cmake的最低版本要求
      cmake_minimum_required (VERSION 3.9)
      
      # 工程名稱、版本、語言
      project (ZipTest VERSION 0.1 LANGUAGES CXX)
      
      # cpp17支持
      set(CMAKE_CXX_STANDARD 17)
      set(CMAKE_CXX_STANDARD_REQUIRED ON)
      
      # 查找依賴庫
      find_package(libzip REQUIRED)
      
      # 將源代碼添加到此項(xiàng)目的可執(zhí)行文件。
      add_executable (${PROJECT_NAME} "main.cpp")
      
      # 鏈接依賴庫
      target_link_libraries(${PROJECT_NAME} PRIVATE libzip::zip)
      

      可以看到內(nèi)容并不多,逐行進(jìn)行解析:

      1. message指令是用來在CMake構(gòu)建的配置階段輸出的,這個(gè)指令非常有用,可以用來檢查一些配置變量。
      2. cmake_minimum_required表示cmake的最低版本要求,CMake的很多特性是隨著版本逐漸增加的,需要保證使用的CMake特性滿足最低版本的要求。
      3. project定義工程名稱、版本和編程語言。
      4. 一些構(gòu)建配置已經(jīng)被CMake給統(tǒng)一好了,例如是否使用std標(biāo)準(zhǔn)庫,使用std標(biāo)準(zhǔn)庫的版本,這里使用C++17的版本:
      set(CMAKE_CXX_STANDARD 17)
      set(CMAKE_CXX_STANDARD_REQUIRED ON)
      
      1. find_package查找依賴庫指令,這里查找的就是libzip庫。只要libzip庫按照我們前文中的方式正確安裝,通過該指令就可以找到該依賴庫。
      2. add_executable則是將源代碼文件添加到項(xiàng)目中,這個(gè)指令具體定義了有哪些源代碼文件。
      3. target_link_libraries指令的意思是鏈接依賴庫,將libzip庫鏈接到該程序中。

      2.2 構(gòu)建配置

      一些構(gòu)建的配置已經(jīng)被CMake修改成通用性配置,例如上面提到了使用C++17的std標(biāo)準(zhǔn)庫。但是如果有一些針對(duì)不同平臺(tái)的特殊配置怎么辦呢?其實(shí)也很簡(jiǎn)單,就像C/C++寫跨平臺(tái)代碼一樣,識(shí)別不同的平臺(tái)進(jìn)行處理。如下構(gòu)建代碼所示,可以先檢測(cè)編譯器是Clang、GUN、Intel還是MSVC;如果是MSVC平臺(tái)的話,就去掉一些警告,增加一些預(yù)編譯頭。

      # 判斷編譯器類型
      message("CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}")
      
      # 判斷編譯器類型
      if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
          message(">> using Clang")
      elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
          message(">> using GCC")
      elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
          message(">> using Intel C++")
      elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
          message(">> using Visual Studio C++")
          # 禁用特定警告
          add_compile_options(/wd4996 /wd4251) 
          # 設(shè)置預(yù)編譯宏
          add_definitions("-DUNICODE" "-D_UNICODE" "-DNOMINMAX") 
      else()
          message(">> unknow compiler.")
      endif()
      

      在上述構(gòu)建代碼中,4996、4251警告是MSVC經(jīng)常提示的警告,但是作用并不是很大,因此很多MSVC項(xiàng)目會(huì)將其去掉;UNICODE和_UNICODE預(yù)處理宏是告訴MSVC使用Unicode字符集;NOMINMAX預(yù)處理宏則是取消Win32的最大最小函數(shù),避免函數(shù)命令沖突。這些都是MSVC項(xiàng)目的常用配置,我們只需要識(shí)別到MSVC平臺(tái),并將其應(yīng)用到CMake指令中即可。

      其實(shí),構(gòu)建的最關(guān)鍵的步驟就在于編譯和鏈接這兩步,不同的編譯器和鏈接器有不同的命令行參數(shù),使用MSVC的GUI去設(shè)置工程的屬性本質(zhì)上也是取不同的命令行進(jìn)行執(zhí)行。也就是說,上述配置代碼是一種通用的寫法,剩下的我們就只用查找資料找到相應(yīng)編譯器和鏈接器的命令行參數(shù)即可。

      2.3 依賴庫配置

      在上例中可以看到,我們引入依賴庫libzip似乎很容易,find_package一下,target_link_libraries一下似乎就可以了。這是因?yàn)槲覀兪褂昧薈Make的目標(biāo)鏈接(Target-based linking)機(jī)制,這也是目前現(xiàn)代CMake的最佳實(shí)踐,Boost、Qt、OpenCV 等項(xiàng)目都提供了這種方式的支持。

      不過,使用這種方式引入依賴庫也是有一定條件的。具體來說,我們?cè)谑褂肅Make構(gòu)建安裝依賴庫的時(shí)候,會(huì)生成諸如“XXXConfig.cmake”的配置文件到安裝目錄,文件中存在諸如add_library或add_executable等命令,就說明該依賴庫的目標(biāo)導(dǎo)出,支持這種目標(biāo)鏈接機(jī)制。當(dāng)然,這種方式比較新,不是所有的庫項(xiàng)目都提供了這種機(jī)制。

      如果沒有提供目標(biāo)鏈接的方式,那么就可以考慮使用傳統(tǒng)的頭文件和庫文件的引入方式,最簡(jiǎn)單無腦的方式就是使用絕對(duì)路徑了:

      # 輸出cmake版本提示
      message(STATUS "The CMAKE_VERSION is ${CMAKE_VERSION}.")
      
      # cmake的最低版本要求
      cmake_minimum_required (VERSION 3.9)
      
      # 工程名稱、版本、語言
      project (ZipTest VERSION 0.1 LANGUAGES CXX)
      
      # cpp17支持
      set(CMAKE_CXX_STANDARD 17)
      set(CMAKE_CXX_STANDARD_REQUIRED ON)
      
      # 添加頭文件的搜索路徑
      include_directories("C:/Work/3rdparty/include")
      
      # 將源代碼添加到此項(xiàng)目的可執(zhí)行文件。
      add_executable (${PROJECT_NAME} "main.cpp")
      
      # 鏈接依賴庫
      target_link_libraries(${PROJECT_NAME} PRIVATE "C:/Work/3rdparty/lib/zip.lib")
      

      其中include_directories是添加庫的頭文件所在的路徑,target_link_libraries則直接鏈接到庫的地址。不過這種使用絕對(duì)路徑的方式實(shí)在太蠢了,不是支持跨平臺(tái),單平臺(tái)的環(huán)境變化都不能支持。稍微方便的一點(diǎn)的方式是將依賴庫的安裝目錄設(shè)置成環(huán)境變量,例如將“C:/Work/3rdparty”設(shè)置成環(huán)境變量GISBasic,那么就可以簡(jiǎn)寫成:

      # ...
      
      # 添加頭文件的搜索路徑
      include_directories($ENV{GISBasic}/include)
      
      # 將源代碼添加到此項(xiàng)目的可執(zhí)行文件。
      add_executable (${PROJECT_NAME} "main.cpp")
      
      # 鏈接依賴庫
      target_link_libraries(${PROJECT_NAME} PRIVATE $ENV{GISBasic}/lib/zip.lib)
      

      這樣做至少可以做到配置的一致性,即使開發(fā)團(tuán)隊(duì)成員每個(gè)人的安裝目錄都不一樣,也能保證工程正常構(gòu)建,只要將GISBasic環(huán)境變量設(shè)置正確。但是這樣做其實(shí)也不能保證跨平臺(tái),很顯然Liunx環(huán)境下并不是.lib文件而是.so文件,而且通常有l(wèi)ib前綴。那么就可以根據(jù)不同操作系統(tǒng)使用不同的變量值進(jìn)行構(gòu)建就可以了,改進(jìn)如下所示:

      # 添加頭文件的搜索路徑
      include_directories($ENV{GISBasic}/include)
      
      # 將源代碼添加到此項(xiàng)目的可執(zhí)行文件。
      add_executable (${PROJECT_NAME} "main.cpp")
      
      # 動(dòng)態(tài)庫前綴與后綴
      IF(CMAKE_SYSTEM_NAME MATCHES "Linux")    
        set(LibraryPrefix lib)
        set(LibraryPostfix so)
      ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Windows")
        set(LibraryPrefix )
        set(LibraryPostfix lib)      
      ENDIF()
      
      # 鏈接依賴庫
      target_link_libraries(${PROJECT_NAME} PRIVATE $ENV{GISBasic}/lib/${LibraryPrefix}zip.${LibraryPostfix})
      

      可以看到使用鏈接目標(biāo)的方式更加簡(jiǎn)潔一點(diǎn),傳統(tǒng)的使用傳統(tǒng)的頭文件和庫文件的引入方式要達(dá)到跨平臺(tái)的效果需要配置更多的內(nèi)容。其實(shí)CMake的依賴庫配置遠(yuǎn)不止這么一點(diǎn)內(nèi)容,不過比較推薦的和比較底層的兩種方式就以上兩種了。其實(shí)不管是哪一種編程語言的項(xiàng)目,依賴庫的配置永遠(yuǎn)是最麻煩的,以后有機(jī)會(huì)再開一章具體講講CMake關(guān)于依賴庫的配置。

      3 構(gòu)建結(jié)果

      上述簡(jiǎn)單的項(xiàng)目的代碼結(jié)構(gòu)如下所示:

      ZipTest
      │   main.cpp
      │   CMakeLists.txt    
      

      還是使用之前構(gòu)建依賴庫的方式使用腳本進(jìn)行構(gòu)建,將構(gòu)建腳本放置到ZipTest目錄下,運(yùn)行如下腳本:

      param( 
          [string]$SourceLocalPath = ".",
          [string]$Generator = "Visual Studio 16 2019"
        )
      
      # 清除舊的構(gòu)建目錄
      $BuildDir = $SourceLocalPath + "/build"
      if (Test-Path $BuildDir) {
          Remove-Item -Path $BuildDir -Recurse -Force
      }
      New-Item -ItemType Directory -Path $BuildDir
      
      # 轉(zhuǎn)到構(gòu)建目錄
      Push-Location $BuildDir
      
      try {
          # 配置CMake  
          cmake .. -G "$Generator" -A x64 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="$InstallDir"
      
          # 構(gòu)建階段,指定構(gòu)建類型
          cmake --build . --config RelWithDebInfo
      }
      finally {
          # 返回原始工作目錄
          Pop-Location
      }
      

      構(gòu)建的exe成果就在Build目錄的子目錄中。其實(shí)現(xiàn)在已經(jīng)可以用IDE可視化構(gòu)建CMake組建的工程了,具體的過程我們放到下一篇再進(jìn)行介紹,這一篇的關(guān)鍵在于我們要如何去寫CMakeLists.txt文件。其實(shí)筆者也認(rèn)為CMake的語法很繁瑣,大寫字母加上下劃線的寫法一點(diǎn)也不美觀,初學(xué)的時(shí)候看到一堆的宏變量頭都大了。不過正如本系列博文一開始就說的,其實(shí)可以不用去關(guān)注這些細(xì)節(jié),也不用去系統(tǒng)的學(xué)習(xí)什么,CMake畢竟只是幫助我們進(jìn)行構(gòu)建工具而已。比如最重要的引用依賴庫的功能,開始的時(shí)候我們只需要知道include_directories包含頭文件,target_link_libraries鏈接庫文件,哪怕寫一堆條件語句,一堆絕對(duì)路徑也沒什么,我們?cè)跇?gòu)建的過程中自然會(huì)思考如何讓我們的構(gòu)建過程更有效率,從而理解CMake的設(shè)計(jì)思路,就知道如何去寫CMakeLists.txt文件了。

      posted @ 2024-09-14 21:18  charlee44  閱讀(303)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 亚洲国产美女精品久久久| 亚洲av成人三区国产精品| 不卡一区二区三区视频播放| 午夜男女爽爽影院免费视频下载| 日本欧美大码a在线观看| 极品人妻少妇一区二区三区| 2020国产欧洲精品网站| 精品成在人线av无码免费看| 亚洲国产亚洲综合在线尤物| 一区二区三区四区亚洲自拍| 亚洲精品国产综合久久一线| 精品国产成人国产在线观看| 黑人猛精品一区二区三区| 激情综合色综合久久丁香| 亚洲欧美综合精品成人导航| 午夜免费无码福利视频麻豆| 亚洲国产高清av网站| 久久综合97丁香色香蕉| 最近日本免费观看高清视频| 日本美女性亚洲精品黄色| 亚洲一区久久蜜臀av| 边添小泬边狠狠躁视频| 久久99精品久久99日本| 精品中文人妻中文字幕| 中文区中文字幕免费看| 国产午夜福利一区二区三区| 在线国产精品中文字幕| 介休市| 国产一区二区不卡精品视频| 丰满无码人妻热妇无码区| 2020无码专区人妻系列日韩| 国产999久久高清免费观看| 日本一二三区视频在线| 国产精品色内内在线播放| 国产精品美女久久久久久麻豆| 少妇人妻偷人精品免费| 国产精品久久久久鬼色| 亚洲综合久久一区二区三区| 丰满无码人妻热妇无码区| 国产av一区二区亚洲精品| 成在人线av无码免费高潮水老板 |