ubuntu 构建c++ 项目 (AI 生成)
将在 Windows 上使用 Visual Studio 解决方案(Solution)和项目(Project)构建的复杂 C++ 应用,迁移到 Ubuntu 上是一个典型的跨平台开发场景。你提到的结构(多个库项目、一个可执行文件项目、库之间的依赖、公共头文件目录)是大型项目的标准实践。
在 Linux/Ubuntu 环境下,我们有对应的概念和更强大的工具来处理这种复杂性。核心思想是使用 构建系统生成器 (Build System Generator),其中最主流、最强大的就是 CMake。
忘记手动调用 g++ 吧,对于大型项目,那是不可行的。我们需要一个能描述项目结构、依赖关系、编译选项的工具,然后让这个工具自动生成用于编译的指令(比如 Makefile)。这正是 Visual Studio 在后台为你做的事情,而在 Linux 上,我们使用 CMake 来明确地完成这个任务。
下面,我将为你分解这个过程。
核心概念的对应关系
首先,我们来理解一下 Windows 和 Ubuntu 在概念上的对应:
Windows (Visual Studio) | Ubuntu/Linux (GCC/CMake) | 解释 |
---|---|---|
解决方案 (.sln) | 根 CMakeLists.txt 文件 | 整个软件的顶层描述,管理所有子项目。 |
项 | 目 (.vcxproj) | 子目录的 CMakeLists.txt 文件 |
静态库 (.lib) | 静态库 (.a) | a 代表 “archive”。编译时,库的代码被完整地复制到最终的可执行文件中。 |
动态链接库 (.dll) | 共享库 (.so) | so 代表 “shared object”。运行时,多个程序可以共享同一个库文件,减小体积。 |
项目依赖关系 | target_link_libraries | 在 CMake 中明确指定一个目标(如你的exe)需要链接哪些库(a, b, c)。 |
附加包含目录 | target_include_directories | 告诉编译器去哪里寻找 #include 的头文件。 |
编译和链接 | make (由 CMake 生成) | 执行实际的编译和链接命令。 |
迁移步骤与项目结构
假设你在 Windows 上的项目结构如下:
MySolution/
├── common_includes/ # 公共头文件
│ └── public_header.h
├── lib_a/ # 库 a
│ ├── a.h
│ └── a.cpp
├── lib_b/ # 库 b (可能依赖 lib_a)
│ ├── b.h
│ └── b.cpp
└── my_app/ # 最终的 exe 项目
└── main.cpp
迁移到 Ubuntu 并使用 CMake 构建,我们会创建一个类似的目录结构,并在关键位置添加 CMakeLists.txt 文件。
推荐的 Ubuntu/CMake 项目结构:
my_project/
├── CMakeLists.txt # [1] 顶层 CMake 文件
├── common_includes/
│ └── public_header.h
├── lib_a/
│ ├── CMakeLists.txt # [2] 库 a 的 CMake 文件
│ ├── a.h
│ └── a.cpp
├── lib_b/
│ ├── CMakeLists.txt # [3] 库 b 的 CMake 文件
│ ├── b.h
│ └── b.cpp
└── app/├── CMakeLists.txt # [4] 可执行文件的 CMake 文件└── main.cpp
如何编写 CMakeLists.txt
CMakeLists.txt 是一个纯文本文件,使用 CMake 的特定语法来描述构建过程。
- 顶层 CMakeLists.txt
这个文件是整个项目的入口,负责设置项目基本信息,并告诉 CMake 去哪些子目录寻找其他的 CMakeLists.txt。
CMake# my_project/CMakeLists.txt# 要求 CMake 的最低版本
cmake_minimum_required(VERSION 3.10)# 定义项目名称
project(MyAwesomeProject VERSION 1.0)# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 将公共头文件目录添加到全局的包含路径中
# 这样所有子项目都能找到 common_includes 里的头文件
include_directories(common_includes)# 告诉 CMake 去处理这些子目录
add_subdirectory(lib_a)
add_subdirectory(lib_b)
add_subdirectory(app)
- lib_a 的 CMakeLists.txt
这个文件定义如何构建库 a。我们把它建成一个共享库 (.so)。
CMake# my_project/lib_a/CMakeLists.txt# add_library 命令用于创建一个库
# a_lib 是我们给这个库起的名字
# SHARED 表示创建共享库 (.so)。如果想创建静态库,用 STATIC (.a)
# a.cpp 是构建这个库所需的源文件
add_library(a_lib SHARED a.cpp)# 如果 a.h 在当前目录,通常需要将当前目录也加入到
# a_lib 这个目标的 "public" 包含路径中。
# 这样,任何链接到 a_lib 的项目都能自动找到 a.h。
target_include_directories(a_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
- lib_b 的 CMakeLists.txt
这个文件定义了库 b。关键在于,库 b 依赖库 a。
CMake# my_project/lib_b/CMakeLists.txt# 创建库 b
add_library(b_lib SHARED b.cpp)# 将当前目录设为 b_lib 的 public 包含路径
target_include_directories(b_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})# *** 这是关键:声明依赖关系 ***
# target_link_libraries 告诉 CMake,b_lib 需要链接到 a_lib。
# CMake 会自动处理链接顺序和头文件路径。
target_link_libraries(b_lib PUBLIC a_lib)
PUBLIC vs PRIVATE: target_link_libraries 中的 PUBLIC 关键字很重要。它意味着:不仅 b_lib 自己需要链接 a_lib,任何链接到 b_lib 的目标(比如我们后面的可执行文件)也需要链接到 a_lib。这完美地处理了依赖传递。
- app (可执行文件) 的 CMakeLists.txt
最后,这个文件定义了如何构建最终的可执行文件,并链接它所需要的所有库。
CMake# my_project/app/CMakeLists.txt# add_executable 用于创建一个可执行文件
# my_app 是可执行文件的名字
# main.cpp 是所需的源文件
add_executable(my_app main.cpp)# *** 链接所有需要的库 ***
# 我们的 my_app 需要库 a 和库 b 的功能。
# 因为库 b 公开地依赖库 a,理论上我们只需要链接库 b 即可。
# 但为了清晰,可以都写上。CMake 很聪明,不会重复链接。
target_link_libraries(my_app PRIVATE b_lib)
编译整个项目的流程
在你配置好所有的 CMakeLists.txt 文件后,编译整个大型项目只需要几个简单的命令。
创建一个构建目录: 这是一个非常好的习惯,叫做“外部构建”(out-of-source build)。它将所有编译生成的文件(Makefile, .o 文件, .so 文件, 可执行文件)与你的源代码隔离开,保持源代码目录的干净。
Bashcd my_project
mkdir build
cd build
运行 CMake: 在 build 目录下,运行 cmake 并指向源代码的根目录(… 代表上一级目录)。CMake 会读取你所有的 CMakeLists.txt 文件,并生成原生的构建文件(在 Linux 上默认是 Makefile)。
Bashcmake ..
如果一切顺利,CMake 会检查你的编译器和环境,并输出配置完成的信息。
运行 make: 现在,build 目录里有了一个 Makefile。你只需要运行 make 命令,它就会根据 Makefile 里的规则,以正确的顺序编译和链接所有的库和可执行文件。
Bashmake
make 会自动处理依赖。如果你只修改了 lib_b 的代码,make 只会重新编译 lib_b 和最终的 app,而不会去动 lib_a,非常高效。
运行你的程序: 编译完成后,你的可执行文件 my_app 和库文件 liba_lib.so, libb_lib.so 都会在 build 目录的相应子文件夹里。
你可以在 build/app 目录下找到你的可执行文件并运行它。
Bash./app/my_app
总结
将一个大型的、多项目的 Windows C++ 解决方案迁移到 Ubuntu,关键在于从 Visual Studio 的思维模式转换到 CMake 的思维模式。
-
核心工具: 使用 CMake 替代 Visual Studio 的解决方案和项目文件来描述项目结构和依赖。
-
项目结构: 保持清晰的目录结构,每个目标(库/可执行文件)在自己的目录里,并拥有一个 CMakeLists.txt。
-
描述依赖:
-
使用 add_library() 定义库。
-
使用 add_executable() 定义可执行文件。
-
使用 target_link_libraries() 定义目标之间的依赖关系。
-
使用 include_directories() 或 target_include_directories() 管理头文件路径。
-
-
编译流程: mkdir build && cd build, cmake …, make。这个三步曲是 Linux 下用 CMake 构建项目的黄金准则。
这个工作流不仅适用于 Ubuntu,也适用于 macOS 甚至 Windows(CMake 可以在 Windows 上生成 Visual Studio 的 .sln 文件!),是现代 C++ 跨平台开发的事实标准。