Cmake·

[#4][Adding a Library][A菌严肃的CMake手记]

美式A菌

美式A菌

66 0

Intro

同样是复习,上一篇中我们讲过几个API

  • CMAKE_CXX_STANDARD
  • CMAKE_CXX_STANDARD_REQUIRED
  • set
  • project(NAME VERSION x.x.x)
  • configure_file()
  • target_include_directories()
  • message

你能否回忆起来呢?回忆不起来的同学们请回顾上一篇内容哦~

这一章我们直接拿官方的代码过来做初始代码Code

Q? & A!

  • 当我拥有一份通用代码需要调用时,要如何加入到构建过程中以及使用它呢?
    • 本篇余下部分将使用CMake提供的add_library,add_subdirectory等函式进行演示与讲解。
  • 本文涉及到链接,链接是什么有什么作用呢?
    • 简而言之如果A文件需要B文件,那么编译时,应该把B文件链接到A文件中,形成一个整体的上下文,这样用起来才不会出错,如果没有链接成完整的上下文,那么将会报出错误某某没有被定义,某某找不到,空指针,大概率连编译过程都跑不通啦

开始

Step1: 看代码

  • 目录结构

  • MathFunctions 是我们抽象出来的Lib,稍后修改完毕要在tutorial.cxx中引用的。

  • MathFunctions/CMakeLists.txt

MathFunctions这个库也是一个独立可用的,所以也需要有CMakeLists.txt来描述项目。


  • MathFunctions/mysqrt.h
// 声明自定义平方根函数的头文件。

// 编译器指令,用于防止头文件被重复包含。它确保头文件内容只会被编译一次。
#pragma once

// 定义了一个命名空间 mathfunctions,用于组织代码,避免命名冲突。
namespace mathfunctions
{
    // namespace detail:在 mathfunctions 命名空间内定义了一个子命名空间 detail,通常用于表示内部实现细节。
    namespace detail
    {
        // 在 detail 命名空间内声明了一个名为 mysqrt 的函数,接受一个 double 类型的参数 x,返回一个 double 类型的结果。
        double mysqrt(double x);
    }
}
  • MathFunctions/mysqrt.cxx

这个cpp实现了mysqrt.h的定义,主要作用是计算平方根。

#include "mysqrt.h"

#include <iostream>

namespace mathfunctions
{
  namespace detail
  {
    double mysqrt(double x)
    {
      if (x <= 0)
      {
        return 0;
      }

      double result = x;

      for (int i = 0; i < 10; ++i)
      {
        if (result <= 0)
        {
          result = 0.1;
        }
        double delta = x - (result * result);
        result = result + 0.5 * delta / result;
        std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
      }
      return result;
    }
  }
}

  • MathFunctions/MathFunctions.h
// 编译器指令,用于防止头文件被重复包含。它确保头文件内容只会被编译一次。
#pragma once

// 命名空间,用于防止命名冲突。
namespace mathfunctions
{
    // 计算平方根
    double sqrt(double x);
}
  • MathFunctions/MathFunctions.cxx
#include "MathFunctions.h"

// 引入同级目录下的mysqrt.h平方根函数的实现
#include "mysqrt.h"

// 实现MathFunctions.h库的函数
namespace mathfunctions
{
  double sqrt(double x)
  {
    // 调用mysqrt.h中的mysqrt函数
    return detail::mysqrt(x);
  }
}

  • /tutorial.cxx
  • /TutorialConfig.h.in
  • /CMakeLists.txt

这三个文件和前一章的内容大致相同所以不在阐述

接下来我们要修改代码将tutorial.cxx中的

  • const double outputValue = sqrt(inputValue);
  • 替换为const double outputValue = mathfunctions::sqrt(inputValue)
  • 并保证编译正常运行结果正确

Step2: 链接可执行文件和库代码

  • MathFunctions/CMakeLists.txt中使用add_library来描述库。
# 用于创建库的命令。
# 定义一个新的库目标,并指定构成该库的源文件。
# 语法:add_library(<name> [<type>] [EXCLUDE_FROM_ALL] <sources>...)
# 参数:
# <name>:库的名称。
# <type>(可选):库的类型,可以是以下之一:
#   STATIC:静态库,编译时将所有代码打包到一个库文件中。
#   SHARED:动态库,运行时链接。
#   MODULE:模块库,通常用于插件,运行时动态加载。
# 如果没有指定 <type>,则默认为 STATIC 或 SHARED,具体取决于 BUILD_SHARED_LIBS 变量的值。

add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)

  • 修改根目录CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

project(Tutorial VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

configure_file(TutorialConfig.h.in TutorialConfig.h)
add_executable(Tutorial tutorial.cxx)
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")


# 向构建添加一个子目录。
# add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
# source_dir 指定源 CMakeLists.txt 和代码文件所在的目录。
# binary_dir 指定放置输出文件的目录。
add_subdirectory(MathFunctions)

# 指定目标链接库的命令。它将一个或多个库链接到指定的目标(如可执行文件或另一个库)。
# target_link_libraries(<target> ... <item>... ...)
# Tutorial 是目标名称。
# MathFunctions 是要链接的库名称。
# PUBLIC 修饰符表示 MathFunctions 库对 Tutorial 目标自身和依赖于 Tutorial 的其他目标都是可见的。
# 除了PUBLIC还有PRIVATE:链接库仅对目标自身可见。
# 还有INTERFACE:链接库仅对依赖于该目标的其他目标可见,目标自身不可见。
target_link_libraries(Tutorial PUBLIC MathFunctions)

# 扫一下头文件。
target_include_directories(Tutorial PUBLIC
                          "${PROJECT_BINARY_DIR}"
                          "${PROJECT_SOURCE_DIR}/MathFunctions"
                          )

Step3: 修改代码,使用库代码

使用我们自己的计算平方根库来替换cmath中提供的平方根函数。

// 注释掉数学库的引入
// #include <cmath>
#include <iostream>
#include <string>

#include "TutorialConfig.h"
// 引入MathFunctions.h库
#include "MathFunctions.h"

int main(int argc, char *argv[])
{
  if (argc < 2)
  {
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  const double inputValue = std::stod(argv[1]);

  // 使用我们自己的库替换标准库的sqrt函数
  // const double outputValue = sqrt(inputValue);
  const double outputValue = mathfunctions::sqrt(inputValue);
  std::cout << "The square root of " << inputValue << " is " << outputValue
            << std::endl;
  return 0;
}
  • 打包测试

Step4: 可选平方根实现

  • 这里我们还需要补充几个函数,这些函数将CMake编译一些库时可以配置成可选的。
  • 关于if,option,target_compile_definitions,target_link_libraries

  • MathFunctions/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

# 用于创建库的命令。
# 定义一个新的库目标,并指定构成该库的源文件。
# 语法:add_library(<name> [<type>] [EXCLUDE_FROM_ALL] <sources>...)
# 参数:
# <name>:库的名称。
# <type>(可选):库的类型,可以是以下之一:
#   STATIC:静态库,编译时将所有代码打包到一个库文件中。
#   SHARED:动态库,运行时链接。
#   MODULE:模块库,通常用于插件,运行时动态加载。
# 如果没有指定 <type>,则默认为 STATIC 或 SHARED,具体取决于 BUILD_SHARED_LIBS 变量的值。
# add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
add_library(MathFunctions MathFunctions.cxx)


# 定义变量,提供一个布尔值,并附带上一个描述性的字符串。
# option(<variable> "<help_text>" [value])
option(USE_MYMATH "Use tutorial provided math implementation" ON)
# 如果 USE_MYMATH 为真,则为 MathFunctions 添加一个编译定义。
if (USE_MYMATH)
    # 通常用于在编译时向编译器传递预处理器宏。
    # 后续程序使用 #ifdef USE_MYMATH 来进行条件编译。
    # target_compile_definitions(<target> <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
    target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
    # 前面提到STATIC类型的库,编译时将所有代码打包到一个库文件中。
    # 静态库在编译时将所有代码打包到一个库文件中,最终生成的可执行文件会包含这些代码。
    # 与动态库不同,静态库在运行时不需要额外的库文件。
    add_library(SqrtLibrary STATIC mysqrt.cxx)
    # 将一个或多个库链接到指定的目标(如可执行文件或另一个库)。
    target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
  • option(USE_MYMATH "Use tutorial provided math implementation" ON)设置了一个变量/宏为USE_MYMATH
  • if (USE_MYMATH)通过if进行编译时的流程控制。
  • 如果为开启状态
    • target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH"),如果USE_MYMATH开启,那么在编译MathFunctions这个Library的时候,将传递USE_MYMATH宏进入其代码。
    • add_library(SqrtLibrary STATIC mysqrt.cxx)添加一个本地库,因为USE_MYMATH为真也就是使用了我们提供的平方根函数。
    • target_link_libraries(MathFunctions PRIVATE SqrtLibrary) 将上句的SqrtLibrary链接到MathFunctions
    • endif()
  • 如果为关闭状态
    • 无事发生
    • endif()

  • MathFunctions.cxx
#include "MathFunctions.h"

#include <cmath>

#ifdef USE_MYMATH
#  include "mysqrt.h"
#endif

namespace mathfunctions {
double sqrt(double x)
{
#ifdef USE_MYMATH
  return detail::mysqrt(x);
#else
  return std::sqrt(x);
#endif
}
}

这里很清晰的可以看到使用USE_MYMATH宏来做了一些事情,比如

  • 如果USE_MYMATH为真则包含我们实现的函数头文件等等,不赘述

打包时我们可以动态通过-DUSE_MYMATH=OFF来动态设置使用那些函数实现。


  • 根目录CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(Tutorial VERSION 1.0)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
configure_file(TutorialConfig.h.in TutorialConfig.h)
add_subdirectory(MathFunctions)
add_executable(Tutorial tutorial.cxx)


# list 扫描 MathFunctions 目录,将其添加/追加(APPEND)到  YAHAHA 列表变量中。
list(APPEND YAHAHA "${PROJECT_SOURCE_DIR}/MathFunctions")
# 将 YAHAHA 列表变量添加到 Tutorial 的包含路径中,用于找到头文件。
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" ${YAHAHA})
# 将 MathFunctions 库链接到 Tutorial 可执行文件中。
target_link_libraries(Tutorial PUBLIC MathFunctions)

总结

  • 其实A菌有种错觉啦,关于链来链去的就好像国中一本编译原理中讲到的文件链接了啦。
  • 好啦,我们在来看一下今天又新学了几个API呢?
    • add_library(): 设定一个程式为Lib库。
    • add_subdirectory(): 为项目增加一个子目录。
    • target_link_libraries(): 将一个或多个库链接到指定的目标(如可执行文件或另一个库)。
    • PROJECT_SOURCE_DIR: 程式源码的目录。
    • if()endif(): 条件判断。
    • list(),APPEND: 扫描和追加。
    • option(): 设定选项。
    • target_compile_definitions(): 向程式添加预处理器宏。
    • cmake -D[OPTION_NAME]=: 为CMakeLists.txt中的option填充值。

所属系列

从当前文章继续阅读它所在合集中的前后内容。

相关文章

优先推荐同专题、同标签和同作者内容,补足热门文章。

评论 0

登录 后参与评论

评论

成为第一个评论的人