C++ 项目组织(使用 gtest、cmake 和 doxygen)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13521618/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ project organisation (with gtest, cmake and doxygen)
提问by rozzy
I am new to programming in general so I decided that I would start by making a simple vector class in C++. However I would like to get in to good habits from the start rather than trying to modify my workflow later on.
我一般不熟悉编程,所以我决定从用 C++ 制作一个简单的向量类开始。但是,我想从一开始就养成良好的习惯,而不是在以后尝试修改我的工作流程。
I currently have only two files vector3.hpp
and vector3.cpp
. This project will slowly start to grow (making it much more of a general linear algebra library) as I become more familiar with everything, so I would like to adopt a "standard" project layout to make life easier later on. So after looking around I have found two ways to go about organizing hpp and cpp files, the first being:
我目前只有两个文件vector3.hpp
和vector3.cpp
. 随着我对所有内容越来越熟悉,这个项目将慢慢开始增长(使其更像是一个通用的线性代数库),所以我想采用“标准”项目布局,让以后的生活更轻松。因此,环顾四周后,我找到了两种组织 hpp 和 cpp 文件的方法,第一种是:
project
└── src
├── vector3.hpp
└── vector3.cpp
and the second being:
第二个是:
project
├── inc
│ └── project
│ └── vector3.hpp
└── src
└── vector3.cpp
Which would you recommend and why?
你会推荐哪个,为什么?
Secondly I would like to use the Google C++ Testing Framework for unit testing my code as it seems fairly easy to use. Do you suggest bundling this with my code, for example in a inc/gtest
or contrib/gtest
folder? If bundled, do you suggest using the fuse_gtest_files.py
script to reduce the number or files, or leaving it as is? If not bundled how is this dependency handled?
其次,我想使用 Google C++ 测试框架对我的代码进行单元测试,因为它看起来很容易使用。您是否建议将其与我的代码捆绑在一起,例如在 ainc/gtest
或contrib/gtest
文件夹中?如果捆绑,您是否建议使用fuse_gtest_files.py
脚本来减少数量或文件,还是保持原样?如果没有捆绑,这个依赖是如何处理的?
When it comes to writing tests, how are these generally organized? I was thinking to have one cpp file for each class (test_vector3.cpp
for example) but all compiled in to one binary so that they can all be run together easily?
在编写测试时,这些测试通常是如何组织的?我想为每个类(test_vector3.cpp
例如)有一个 cpp 文件,但都编译成一个二进制文件,以便它们可以轻松地一起运行?
Since the gtest library is generally build using cmake and make, I was thinking that it would make sense for my project to also be built like this? If I decided to use the following project layout:
由于 gtest 库通常是使用 cmake 和 make 构建的,我想我的项目也这样构建是否有意义?如果我决定使用以下项目布局:
├── CMakeLists.txt
├── contrib
│ └── gtest
│ ├── gtest-all.cc
│ └── gtest.h
├── docs
│ └── Doxyfile
├── inc
│ └── project
│ └── vector3.cpp
├── src
│ └── vector3.cpp
└── test
└── test_vector3.cpp
How would the CMakeLists.txt
have to look so that it can either build just the library or the library and the tests? Also I have seen quite a few projects that have a build
and a bin
directory. Does the build happen in the build directory and then the binaries moved out in to the bin directory? Would the binaries for the tests and the library live in the same place? Or would it make more sense to structure it as follows:
必须如何CMakeLists.txt
看待才能仅构建库或库和测试?我也看到了很多有一个build
和一个bin
目录的项目。构建是否发生在构建目录中,然后二进制文件移出到 bin 目录?测试和库的二进制文件会在同一个地方吗?或者将其结构如下更有意义:
test
├── bin
├── build
└── src
└── test_vector3.cpp
I would also like to use doxygen to document my code. Is it possible to get this to automatically run with cmake and make?
我还想使用 doxygen 来记录我的代码。是否有可能让它与 cmake 和 make 一起自动运行?
Sorry for so many questions, but I have not found a book on C++ that satisfactorily answers these type of questions.
抱歉问了这么多问题,但我还没有找到一本关于 C++ 的书来令人满意地回答这些类型的问题。
采纳答案by pmr
C++ build systems are a bit of a black art and the older the project the more weird stuff you can find so it is not surprising that a lot of questions come up. I'll try to walk through the questions one by one and mention some general things regarding building C++ libraries.
C++ 构建系统有点像黑色艺术,项目越老,你能找到的东西就越奇怪,所以出现很多问题也就不足为奇了。我将尝试一一解决这些问题,并提及有关构建 C++ 库的一些一般问题。
Separating headers and cpp files in directories. This is only
essential if you are building a component that is supposed to be used
as a library as opposed to an actual application. Your headers are the
basis for users to interact with what you offer and must be
installed. This means they have to be in a subdirectory (no-one wants
lots of headers ending up in top-level /usr/include/
) and your
headers must be able to include themselves with such a setup.
分隔目录中的头文件和 cpp 文件。仅当您正在构建一个应该用作库而不是实际应用程序的组件时,这才是必不可少的。您的标题是用户与您提供的内容进行交互的基础,并且必须安装。这意味着它们必须在一个子目录中(没有人想要大量的标题以顶级结尾/usr/include/
)并且您的标题必须能够在这样的设置中包含自己。
└── prj
├── include
│?? └── prj
│?? ├── header2.h
│?? └── header.h
└── src
└── x.cpp
works well, because include paths work out and you can use easy globbing for install targets.
效果很好,因为包含路径可以解决,并且您可以使用简单的通配符来安装目标。
Bundling dependencies: I think this largely depends on the ability of
the build system to locate and configure dependencies and how
dependent your code on a single version is. It also depends on how
able your users are and how easy is the dependency to install on their
platform. CMake comes with a find_package
script for Google
Test. This makes things a lot easier. I would go with bundling only
when necessary and avoid it otherwise.
捆绑依赖:我认为这在很大程度上取决于构建系统定位和配置依赖的能力,以及你的代码对单个版本的依赖程度。它还取决于您的用户的能力以及在其平台上安装依赖项的难易程度。CMake 附带了一个find_package
用于 Google 测试的脚本。这让事情变得容易多了。我只会在必要时才进行捆绑,否则就避免捆绑。
How to build: Avoid in-source builds. CMake makes out of source-builds easy and it makes life a lot easier.
如何构建:避免源代码构建。CMake 使源代码构建变得容易,并使生活变得更加轻松。
I suppose you also want to use CTest to run tests for your system (it
also comes with build-in support for GTest). An important decision for
directory layout and test organization will be: Do you end up with
subprojects? If so, you need some more work when setting up CMakeLists
and should split your subprojects into subdirectories, each with its
own include
and src
files. Maybe even their own doxygen runs and
outputs (combining multiple doxygen projects is possible, but not easy
or pretty).
我想您还想使用 CTest 为您的系统运行测试(它还带有对 GTest 的内置支持)。目录布局和测试组织的一个重要决定将是:你最终得到子项目吗?如果是这样,您在设置时CMakeLists,应你的子项目有自己分裂成子目录,每个人都需要一些更多的工作include
和src
文件。甚至可能是他们自己的 doxygen 运行和输出(组合多个 doxygen 项目是可能的,但并不容易或漂亮)。
You will end up with something like this:
你最终会得到这样的结果:
└── prj
├── CMakeLists.txt <-- (1)
├── include
│?? └── prj
│?? ├── header2.hpp
│?? └── header.hpp
├── src
│?? ├── CMakeLists.txt <-- (2)
│?? └── x.cpp
└── test
├── CMakeLists.txt <-- (3)
├── data
│?? └── testdata.yyy
└── testcase.cpp
where
在哪里
- (1) configures dependencies, platform specifics and output paths
- (2) configures the library you are going to build
- (3) configures the test executables and test-cases
- (1) 配置依赖项、平台细节和输出路径
- (2) 配置你要构建的库
- (3) 配置测试可执行文件和测试用例
In case you have sub-components I would suggest adding another hierarchy and use the tree above for each sub-project. Then things get tricky, because you need to decide if sub-components search and configure their dependencies or if you do that in the top-level. This should be decided on a case-by-case basis.
如果您有子组件,我建议添加另一个层次结构并为每个子项目使用上面的树。然后事情变得棘手,因为您需要决定是子组件搜索和配置它们的依赖项,还是在顶层执行此操作。这应该根据具体情况来决定。
Doxygen: After you managed to go through the configuration dance of
doxygen, it is trivial to use CMake add_custom_command
to add a
doc target.
Doxygen:在你完成了 doxygen 的配置舞蹈之后,使用 CMakeadd_custom_command
添加一个 doc 目标是微不足道的。
This is how my projects end up and I have seen some very similar projects, but of course this is no cure all.
这就是我的项目的最终结果,我也看到了一些非常相似的项目,但当然这并不能解决所有问题。
AddendumAt some point you will want to generate a config.hpp
file that contains a version define and maybe a define to some version
control identifier (a Git hash or SVN revision number). CMake has
modules to automate finding that information and to generate
files. You can use CMake's configure_file
to replace variables in a
template file with variables defined inside the CMakeLists.txt
.
附录在某些时候,您会想要生成一个config.hpp
文件,其中包含版本定义,也可能是某个版本控制标识符(Git 哈希或 SVN 修订号)的定义。CMake 具有自动查找信息和生成文件的模块。您可以使用 CMakeconfigure_file
将模板文件中的变量替换为CMakeLists.txt
.
If you are building libraries you will also need an export define to
get the difference between compilers right, e.g. __declspec
on MSVC
and visibility
attributes on GCC/clang.
如果您正在构建库,您还需要一个导出定义来正确区分编译器之间的差异,例如__declspec
MSVC 和visibility
GCC/clang上的属性。
回答by Mikael Persson
As a starter, there are some conventional names for directories that you cannot ignore, these are based on the long tradition with the Unix file system. These are:
首先,有一些您不能忽略的传统目录名称,这些名称基于 Unix 文件系统的悠久传统。这些是:
trunk
├── bin : for all executables (applications)
├── lib : for all other binaries (static and shared libraries (.so or .dll))
├── include : for all header files
├── src : for source files
└── doc : for documentation
It is probably a good idea to stick to this basic layout, at least at the top-level.
坚持这种基本布局可能是个好主意,至少在顶层。
About splitting the header files and source files (cpp), both schemes are fairly common. However, I tend to prefer keeping them together, it is just more practical on day-to-day tasks to have the files together. Also, when all the code is under one top-level folder, i.e., the trunk/src/
folder, you can notice that all the other folders (bin, lib, include, doc, and maybe some test folder) at the top level, in addition to the "build" directory for an out-of-source build, are all folders that contain nothing more than files that are generated in the build process. And thus, only the src folder needs to be backed up, or much better, kept under a version control system / server (like Git or SVN).
关于拆分头文件和源文件 (cpp),这两种方案都相当常见。但是,我倾向于将它们放在一起,在日常任务中将文件放在一起更实用。此外,当所有代码都在一个顶级文件夹下时,即trunk/src/
文件夹,您会注意到所有其他文件夹(bin、lib、include、doc,也许还有一些 test 文件夹)都在顶级文件夹中,除了外源构建的“构建”目录是所有文件夹,仅包含在构建过程中生成的文件。因此,只需要备份 src 文件夹,或者更好的是,将其保存在版本控制系统/服务器(如 Git 或 SVN)下。
And when it comes to installing your header files on the destination system (if you want to eventually distribute your library), well, CMake has a command for installing files (implicitly creates a "install" target, to do "make install") which you can use to put all the headers into the /usr/include/
directory. I just use the following cmake macro for this purpose:
当谈到在目标系统上安装头文件时(如果你想最终分发你的库),好吧,CMake 有一个安装文件的命令(隐式创建一个“安装”目标,执行“make install”)您可以使用将所有标题放入/usr/include/
目录中。为此,我仅使用以下 cmake 宏:
# custom macro to register some headers as target for installation:
# setup_headers("/path/to/header/something.h" "/relative/install/path")
macro(setup_headers HEADER_FILES HEADER_PATH)
foreach(CURRENT_HEADER_FILE ${HEADER_FILES})
install(FILES "${SRCROOT}${CURRENT_HEADER_FILE}" DESTINATION "${INCLUDEROOT}${HEADER_PATH}")
endforeach(CURRENT_HEADER_FILE)
endmacro(setup_headers)
Where SRCROOT
is a cmake variable that I set to the src folder, and INCLUDEROOT
is cmake variable that I configure to wherever to headers need to go. Of course, there are many other ways to do this, and I'm sure my way is not the best. The point is, there is no reason to split the headers and sources just because only the headers need to be installed on the target system, because it is very easy, especially with CMake (or CPack), to pick out and configure the headers to be installed without having to have them in a separate directory. And this is what I have seen in most libraries.
哪里SRCROOT
是CMake的变量,我设置为src文件夹,并且INCLUDEROOT
是CMake的变量,我设置到哪里,以头需要去。当然,还有很多其他方法可以做到这一点,我确信我的方法不是最好的。关键是,没有理由仅仅因为只需要在目标系统上安装头文件就拆分头文件和源文件,因为它很容易,尤其是使用 CMake(或 CPack),挑选和配置头文件无需将它们放在单独的目录中即可安装。这就是我在大多数图书馆中看到的。
Quote: Secondly I would like to use the Google C++ Testing Framework for unit testing my code as it seems fairly easy to use. Do you suggest bundling this with my code, for example in a "inc/gtest" or "contrib/gtest" folder? If bundled, do you suggest using the fuse_gtest_files.py script to reduce the number or files, or leaving it as is? If not bundled how is this dependency handled?
Quote: 其次,我想使用 Google C++ 测试框架对我的代码进行单元测试,因为它看起来相当容易使用。您是否建议将其与我的代码捆绑在一起,例如在“inc/gtest”或“contrib/gtest”文件夹中?如果捆绑,您是否建议使用 fuse_gtest_files.py 脚本来减少数量或文件,还是保持原样?如果没有捆绑,这个依赖是如何处理的?
Don't bundle dependencies with your library. This is generally a pretty horrible idea, and I always hate it when I'm stuck trying to build a library that did that. It should be your last resort, and beware of the pitfalls. Often, people bundle dependencies with their library either because they target a terrible development environment (e.g., Windows), or because they only support an old (deprecated) version of the library (dependency) in question. The main pitfall is that your bundled dependency might clash with already installed versions of the same library / application (e.g., you bundled gtest, but the person trying to build your library already has a newer (or older) version of gtest already installed, then the two might clash and give that person a very nasty headache). So, as I said, do it at your own risk, and I would say only as a last resort. Asking the people to install a few dependencies before being able to compile your library is a much lesser evil than trying to resolve clashes between your bundled dependencies and existing installations.
不要将依赖项与您的库捆绑在一起。这通常是一个非常可怕的想法,当我试图构建一个这样做的库时,我总是讨厌它。这应该是你最后的手段,并提防陷阱。通常,人们将依赖项与他们的库捆绑在一起,要么是因为他们的目标是糟糕的开发环境(例如,Windows),要么是因为他们只支持相关库(依赖项)的旧(弃用)版本。主要的缺陷是您捆绑的依赖项可能与同一库/应用程序的已安装版本发生冲突(例如,您捆绑了 gtest,但尝试构建您的库的人已经安装了更新(或旧)版本的 gtest,然后两者可能会发生冲突并让那个人非常头疼)。所以,正如我所说的,风险自负,我只能说作为最后的手段。在能够编译您的库之前要求人们安装一些依赖项比尝试解决捆绑的依赖项和现有安装之间的冲突要小得多。
Quote: When it comes to writing tests, how are these generally organised? I was thinking to have one cpp file for each class (test_vector3.cpp for example) but all compiled in to one binary so that they can all be run together easily?
Quote: 在编写测试时,这些通常是如何组织的?我想为每个类(例如 test_vector3.cpp)创建一个 cpp 文件,但所有文件都编译为一个二进制文件,以便它们可以轻松地一起运行?
One cpp file per class (or small cohesive group of classes and functions) is more usual and practical in my opinion. However, definitely, don't compile them all into one binary just so that "they can all be run together". That's a really bad idea. Generally, when it comes to coding, you want to split things up as much as it is reasonable to do so. In the case of unit-tests, you don't want one binary to run all the tests, because that means that any little change that you make to anything in your library is likely to cause a near total recompilation of that unit-test program, and that's just minutes / hours lost waiting for recompilation. Just stick to a simple scheme: 1 unit = 1 unit-test program. Then, use either a script or a unit-test framework (such as gtest and/or CTest) to run all the test programs and report to failure/success rates.
在我看来,每个类(或一组紧密结合的类和函数)使用一个 cpp 文件更为常见和实用。但是,绝对不要将它们全部编译成一个二进制文件,只是为了“它们可以一起运行”。这真是个坏主意。一般来说,当涉及到编码时,您希望尽可能地将事情分开。在单元测试的情况下,您不希望一个二进制文件运行所有测试,因为这意味着您对库中的任何内容所做的任何微小更改都可能导致该单元测试程序几乎完全重新编译,这只是等待重新编译的几分钟/几小时。只需坚持一个简单的方案:1 个单元 = 1 个单元测试程序。然后,
Quote: Since the gtest library is generally build using cmake and make, I was thinking that it would make sense for my project to also be built like this? If I decided to use the following project layout:
Quote: 由于 gtest 库通常是使用 cmake 和 make 构建的,我想我的项目也这样构建是否有意义?如果我决定使用以下项目布局:
I would rather suggest this layout:
我宁愿建议这种布局:
trunk
├── bin
├── lib
│ └── project
│ └── libvector3.so
│ └── libvector3.a products of installation / building
├── docs
│ └── Doxyfile
├── include
│ └── project
│ └── vector3.hpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── src
│ └── CMakeLists.txt
│ └── Doxyfile.in
│ └── project part of version-control / source-distribution
│ └── CMakeLists.txt
│ └── vector3.hpp
│ └── vector3.cpp
│ └── test
│ └── test_vector3.cpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── build
└── test working directories for building / testing
└── test_vector3
A few things to notice here. First, the sub-directories of your src directory should mirror the sub-directories of your include directory, this is just to keep things intuitive (also, try to keep your sub-directory structure reasonably flat (shallow), because deep nesting of folders is often more of a hassle than anything else). Second, the "include" directory is just an installation directory, its contents are just whatever headers are picked out of the src directory.
这里有几点需要注意。首先,你的 src 目录的子目录应该反映你的 include 目录的子目录,这只是为了让事情直观(同时,尽量保持你的子目录结构合理平坦(浅),因为文件夹的深层嵌套通常比其他任何事情都更麻烦)。其次,“include”目录只是一个安装目录,它的内容就是从 src 目录中挑选出来的任何头文件。
Third, the CMake system is intended to be distributed over the source sub-directories, not as one CMakeLists.txt file at the top-level. This keeps things local, and that's good (in the spirit of splitting things up into independent pieces). If you add a new source, a new header, or a new test program, all you need is to edit one small and simple CMakeLists.txt file in the sub-directory in question, without affecting anything else. This also allows you to restructure the directories with ease (CMakeLists are local and contained in the sub-directories being moved). The top-level CMakeLists should contain most of the top-level configurations, such as setting up destination directories, custom commands (or macros), and finding packages installed on the system. The lower-level CMakeLists should contain only simple lists of headers, sources, and unit-test sources, and the cmake commands that register them to compilation targets.
第三,CMake 系统旨在分布在源子目录中,而不是作为顶层的一个 CMakeLists.txt 文件。这使事情保持本地化,这很好(本着将事情分成独立部分的精神)。如果您添加新源、新头文件或新测试程序,您只需要在相关子目录中编辑一个小而简单的 CMakeLists.txt 文件,而不会影响其他任何内容。这也允许您轻松地重组目录(CMakeLists 是本地的并包含在被移动的子目录中)。顶级 CMakeLists 应包含大多数顶级配置,例如设置目标目录、自定义命令(或宏)以及查找系统上安装的包。较低级别的 CMakeLists 应该只包含简单的标题、来源、
Quote: How would the CMakeLists.txt have to look so that it can either build just the library or the library and the tests?
Quote: CMakeLists.txt 必须如何显示,以便它可以只构建库或库和测试?
Basic answer is that CMake allows you to specifically exclude certain targets from "all" (which is what is built when you type "make"), and you can also create specific bundles of targets. I can't do a CMake tutorial here, but it is fairly straight forward to find out by yourself. In this specific case, however, the recommended solution is, of course, to use CTest, which is just an additional set of commands that you can use in the CMakeLists files to register a number of targets (programs) that are marked as unit-tests. So, CMake will put all the tests in a special category of builds, and that is exactly what you asked for, so, problem solved.
基本答案是 CMake 允许您从“全部”(这是您键入“make”时构建的内容)中明确排除某些目标,并且您还可以创建特定的目标包。我不能在这里做一个 CMake 教程,但你自己找出来是相当直接的。然而,在这种特定情况下,推荐的解决方案当然是使用 CTest,这只是一组额外的命令,您可以在 CMakeLists 文件中使用它们来注册许多标记为单元的目标(程序)-测试。因此,CMake 会将所有测试放在一个特殊的构建类别中,这正是您所要求的,因此问题解决了。
Quote: Also I have seen quite a few projects that have a build ad a bin directory. Does the build happen in the build directory and then the binaries moved out in to the bin directory? Would the binaries for the tests and the library live in the same place? Or would it make more sense to structure it as follows:
Quote: 此外,我还看到很多项目都有一个构建广告和一个 bin 目录。构建是否发生在构建目录中,然后二进制文件移出到 bin 目录?测试和库的二进制文件会在同一个地方吗?或者将其结构如下更有意义:
Having a build directory outside the source ("out-of-source" build) is really the only sane thing to do, it is the de facto standard these days. So, definitely, have a separate "build" directory, outside the source directory, just as the CMake people recommend, and as every programmer I have ever met does. As for the bin directory, well, that is a convention, and it is probably a good idea to stick to it, as I said in the beginning of this post.
在源代码之外拥有一个构建目录(“源外”构建)确实是唯一明智的做法,这是当今事实上的标准。所以,当然,有一个单独的“build”目录,在源目录之外,就像 CMake 人推荐的那样,就像我见过的每个程序员一样。至于 bin 目录,嗯,这是一个约定,坚持它可能是一个好主意,正如我在本文开头所说的。
Quote: I would also like to use doxygen to document my code. Is it possible to get this to automatically run with cmake and make?
Quote: 我还想使用 doxygen 来记录我的代码。是否有可能让它与 cmake 和 make 一起自动运行?
Yes. It is more than possible, it is awesome. Depending on how fancy you want to get, there are several possibilities. CMake does have a module for Doxygen (i.e., find_package(Doxygen)
) which allows you to register targets that will run Doxygen on some files. If you want to do more fancy things, like updating the version number in the Doxyfile, or automatically entering a date / author stamps for source files and so on, it is all possible with a bit of CMake kung-fu. Generally, doing this will involve that you keep a source Doxyfile (e.g., the "Doxyfile.in" that I put in the folder layout above) which has tokens to be found and replaced by CMake's parsing commands. In my top-level CMakeLists file, you will find one such piece of CMake kung-fu that does a few fancy things with cmake-doxygen together.
是的。这是不可能的,太棒了。根据您想要的花哨程度,有几种可能性。CMake 确实有一个 Doxygen 模块(即find_package(Doxygen)
),它允许您注册将在某些文件上运行 Doxygen 的目标。如果你想做更多奇特的事情,比如更新 Doxyfile 中的版本号,或者自动输入源文件的日期/作者戳等等,这一切都可以通过一点 CMake 功夫来实现。通常,这样做会涉及到您保留一个源 Doxyfile(例如,我放在上面文件夹布局中的“Doxyfile.in”),其中包含要找到的标记,并由 CMake 的解析命令替换。在我的顶级 CMakeLists 文件中,您会发现一个这样的 CMake 功夫片,它与 cmake-doxygen 一起做一些奇特的事情。
回答by Fraser
Structuring the project
构建项目
I would generally favour the following:
我通常赞成以下几点:
├── CMakeLists.txt
|
├── docs/
│ └── Doxyfile
|
├── include/
│ └── project/
│ └── vector3.hpp
|
├── src/
└── project/
└── vector3.cpp
└── test/
└── test_vector3.cpp
This means that you have a very clearly defined set of API files for your library, and the structure means that clients of your library would do
这意味着您为您的库定义了一组非常明确的 API 文件,并且该结构意味着您的库的客户端可以执行
#include "project/vector3.hpp"
rather than the less explicit
而不是不那么明确
#include "vector3.hpp"
I like the structure of the /src tree to match that of the /include tree, but that's personal preference really. However, if your project expands to contain subdirectories within /include/project, it would generally help to match those inside the /src tree.
我喜欢 /src 树的结构与 /include 树的结构相匹配,但这确实是个人喜好。但是,如果您的项目扩展为包含 /include/project 中的子目录,则通常有助于匹配 /src 树中的子目录。
For the tests, I favour keeping them "close" to the files they test, and if you do end up with subdirectories within /src, it's a pretty easy paradigm for others to follow if they want to find a given file's test code.
对于测试,我倾向于让它们“靠近”他们测试的文件,如果你最终在 /src 中找到了子目录,那么如果其他人想要找到给定文件的测试代码,那么这是一个非常简单的范例。
Testing
测试
Secondly I would like to use the Google C++ Testing Framework for unit testing my code as it seems fairly easy to use.
其次,我想使用 Google C++ 测试框架对我的代码进行单元测试,因为它看起来很容易使用。
Gtest is indeed simple to use and is fairly comprehensive in terms of its capabilities. It can be used alongside gmockvery easily to extend its capabilities, but my own experiences with gmock have been less favourable. I'm quite prepared to accept that this may well be down to my own shortcomings, but gmock tests tends to be more difficult to create, and much more fragile / difficult to maintain. A big nail in the gmock coffin is that it really doesn't play nice with smart pointers.
Gtest 确实易于使用,并且在其功能方面相当全面。它可以很容易地与gmock一起使用以扩展其功能,但我自己对 gmock 的体验不太好。我已经准备好接受这很可能归结为我自己的缺点,但是 gmock 测试往往更难以创建,并且更脆弱/难以维护。gmock 棺材上的一个大钉子是它真的不适合智能指针。
This is a very trivial and subjective answer to a huge question (which probably doesn't really belong on S.O.)
这是对一个大问题的非常琐碎和主观的答案(这可能并不真正属于 SO)
Do you suggest bundling this with my code, for example in a "inc/gtest" or "contrib/gtest" folder? If bundled, do you suggest using the fuse_gtest_files.py script to reduce the number or files, or leaving it as is? If not bundled how is this dependency handled?
您是否建议将其与我的代码捆绑在一起,例如在“inc/gtest”或“contrib/gtest”文件夹中?如果捆绑,您是否建议使用 fuse_gtest_files.py 脚本来减少数量或文件,还是保持原样?如果没有捆绑,这个依赖是如何处理的?
I prefer using CMake's ExternalProject_Add
module. This avoids you having to keep gtest source code in your repository, or installing it anywhere. It is downloaded and built in your build tree automatically.
我更喜欢使用 CMake 的ExternalProject_Add
模块。这避免了您必须将 gtest 源代码保存在您的存储库中,或将其安装在任何地方。它会自动下载并构建在您的构建树中。
See my answer dealing with the specifics here.
请参阅我在这里处理细节的答案。
When it comes to writing tests, how are these generally organised? I was thinking to have one cpp file for each class (test_vector3.cpp for example) but all compiled in to one binary so that they can all be run together easily?
在编写测试时,这些测试通常是如何组织的?我想为每个类(例如 test_vector3.cpp)创建一个 cpp 文件,但所有文件都编译为一个二进制文件,以便它们可以轻松地一起运行?
Good plan.
好计划。
Building
建筑
I'm a fan of CMake, but as with your test-related questions, S.O. is probably not the best place to ask for opinions on such a subjective issue.
我是 CMake 的粉丝,但与您的测试相关问题一样,SO 可能不是就此类主观问题征求意见的最佳场所。
How would the CMakeLists.txt have to look so that it can either build just the library or the library and the tests?
CMakeLists.txt 必须如何显示才能仅构建库或库和测试?
add_library(ProjectLibrary <All library sources and headers>)
add_executable(ProjectTest <All test files>)
target_link_libraries(ProjectTest ProjectLibrary)
The library will appear as a target "ProjectLibrary", and the test suite as a target "ProjectTest". By specifying the library as a dependency of the test exe, building the test exe will automatically cause the library to be rebuilt if it is out of date.
该库将显示为目标“ProjectLibrary”,而测试套件将显示为目标“ProjectTest”。通过将库指定为测试exe的依赖项,构建测试exe会自动导致库如果过期则重新构建。
Also I have seen quite a few projects that have a build ad a bin directory. Does the build happen in the build directory and then the binaries moved out in to the bin directory? Would the binaries for the tests and the library live in the same place?
我也看到了很多项目都有一个构建和一个 bin 目录。构建是否发生在构建目录中,然后二进制文件移出到 bin 目录?测试和库的二进制文件会在同一个地方吗?
CMake recommends "out-of-source" builds, i.e. you create your own build directory outside the project and run CMake from there. This avoids "polluting" your source tree with build files, and is highly desirable if you're using a vcs.
CMake 推荐“out-of-source”构建,即您在项目外创建自己的构建目录并从那里运行 CMake。这避免了构建文件“污染”您的源代码树,如果您使用 vcs,这是非常可取的。
You canspecify that the binaries are moved or copied to a different directory once built, or that they are created by default in another directory, but there's generally no need. CMake provides comprehensive ways to install your project if desired, or make it easy for other CMake projects to "find" the relevant files of your project.
您可以指定在构建后将二进制文件移动或复制到不同的目录,或者默认情况下在另一个目录中创建它们,但通常没有必要。如果需要,CMake 提供了安装项目的综合方法,或者让其他 CMake 项目更容易“找到”项目的相关文件。
With regards to CMake's own support for finding and executing gtest tests, this would largely be inappropriate if you build gtest as part of your project. The FindGtest
module is really designed to be used in the case where gtest has been built separately outside of your project.
关于 CMake 自己对查找和执行 gtest 测试的支持,如果您将 gtest 作为项目的一部分构建,这在很大程度上是不合适的。该FindGtest
模块真正设计用于在项目之外单独构建 gtest 的情况。
CMake provides its own test framework (CTest), and ideally, every gtest case would be added as a CTest case.
CMake 提供了自己的测试框架 (CTest),理想情况下,每个 gtest 案例都将作为 CTest 案例添加。
However, the GTEST_ADD_TESTS
macro provided by FindGtest
to allow easy addition of gtest cases as individual ctest cases is somewhat lacking in that it doesn't work for gtest's macros other than TEST
and TEST_F
. Value-or Type-parameterisedtests using TEST_P
, TYPED_TEST_P
, etc. aren't handled at all.
但是,由GTEST_ADD_TESTS
提供的FindGtest
允许轻松添加 gtest 案例作为单个 ctest 案例的宏有些缺乏,因为它不适用于 gtest 的除TEST
and之外的宏TEST_F
。 价值-或类型,参数化的测试使用TEST_P
,TYPED_TEST_P
等等都没有得到处理。
The problem doesn't have an easy solution that I know of. The most robust way to get a list of gtest cases is to execute the test exe with the flag --gtest_list_tests
. However, this can only be done once the exe is built, so CMake can't make use of this. Which leaves you with two choices; CMake must try to parse C++ code to deduce the names of the tests (non-trivial in the extreme if you want to take into account all gtest macros, commented-out tests, disabled tests), or test cases are added by hand to the CMakeLists.txt file.
这个问题没有我知道的简单解决方案。获取 gtest 用例列表的最可靠方法是执行带有标志的 test exe --gtest_list_tests
。但是,这只能在构建 exe 后才能完成,因此 CMake 无法使用它。这让您有两种选择;CMake 必须尝试解析 C++ 代码以推导出测试的名称(如果您想考虑所有 gtest 宏、注释掉的测试、禁用的测试,则极端情况下非常重要),或者手动添加测试用例到CMakeLists.txt 文件。
I would also like to use doxygen to document my code. Is it possible to get this to automatically run with cmake and make?
我还想使用 doxygen 来记录我的代码。是否有可能让它与 cmake 和 make 一起自动运行?
Yes - although I have no experience on this front. CMake provides FindDoxygen
for this purpose.
是的 - 虽然我在这方面没有经验。CMake 提供FindDoxygen
了这个目的。
回答by oLen
In addition to the other (excellent) answers, I am going to describe a structure I've been using for relatively large-scaleprojects.
I am not going to address the subquestion about Doxygen, since I would just repeat what is said in the other answers.
除了其他(优秀的)答案之外,我将描述我一直用于相对大型项目的结构。
我不打算解决有关 Doxygen 的子问题,因为我只会重复其他答案中所说的内容。
Rationale
基本原理
For modularity and maintainability, the project is organized as a set of small units. For clarity, let's name them UnitX, with X = A, B, C, ... (but they can have any general name). The directory structure is then organized to reflect this choice, with the possibility to group units if necessary.
为了模块化和可维护性,项目被组织成一组小单元。为清楚起见,让我们将它们命名为 UnitX,其中 X = A、B、C、...(但它们可以具有任何通用名称)。然后组织目录结构以反映此选择,并在必要时可以对单元进行分组。
Solution
解决方案
The basic directory layout is the following (content of units is detailed later on):
基本目录布局如下(单元内容后面详述):
project
├── CMakeLists.txt
├── UnitA
├── UnitB
├── GroupA
│ └── CMakeLists.txt
│ └── GroupB
│ └── CMakeLists.txt
│ └── UnitC
│ └── UnitD
│ └── UnitE
project/CMakeLists.txt
could contain the following:
project/CMakeLists.txt
可能包含以下内容:
cmake_minimum_required(VERSION 3.0.2)
project(project)
enable_testing() # This will be necessary for testing (details below)
add_subdirectory(UnitA)
add_subdirectory(UnitB)
add_subdirectory(GroupA)
and project/GroupA/CMakeLists.txt
:
和project/GroupA/CMakeLists.txt
:
add_subdirectory(GroupB)
add_subdirectory(UnitE)
and project/GroupB/CMakeLists.txt
:
和project/GroupB/CMakeLists.txt
:
add_subdirectory(UnitC)
add_subdirectory(UnitD)
Now to the structure of the different units (let's take, as an example, UnitD)
现在来看看不同单元的结构(让我们以 UnitD 为例)
project/GroupA/GroupB/UnitD
├── README.md
├── CMakeLists.txt
├── lib
│ └── CMakeLists.txt
│ └── UnitD
│ └── ClassA.h
│ └── ClassA.cpp
│ └── ClassB.h
│ └── ClassB.cpp
├── test
│ └── CMakeLists.txt
│ └── ClassATest.cpp
│ └── ClassBTest.cpp
│ └── [main.cpp]
To the different components:
对于不同的组件:
- I like having source (
.cpp
) and headers (.h
) in the same folder. This avoids a duplicate directory hierarchy, makes maintenance easier. For installation, it is no problem (especially with CMake) to just filter the header files. - The role of the directory
UnitD
is to later on allow including files with#include <UnitD/ClassA.h>
. Also, when installing this unit, you can just copy the directory structure as is. Note that you can also organize your source files in subdirectories. - I like a
README
file to summarize what the unit is about and specify useful information about it. CMakeLists.txt
could simply contain:add_subdirectory(lib) add_subdirectory(test)
lib/CMakeLists.txt
:project(UnitD) set(headers UnitD/ClassA.h UnitD/ClassB.h ) set(sources UnitD/ClassA.cpp UnitD/ClassB.cpp ) add_library(${TARGET_NAME} STATIC ${headers} ${sources}) # INSTALL_INTERFACE: folder to which you will install a directory UnitD containing the headers target_include_directories(UnitD PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> PUBLIC $<INSTALL_INTERFACE:include/SomeDir> ) target_link_libraries(UnitD PUBLIC UnitA PRIVATE UnitC )
Here, note that it is not necessary to tell CMake that we want the include directories for
UnitA
andUnitC
, as this was already specified when configuring those units. Also,PUBLIC
will tell all targets that depend onUnitD
that they should automatically include theUnitA
dependency, whileUnitC
won't be required then (PRIVATE
).test/CMakeLists.txt
(see further below if you want to use GTest for it):project(UnitDTests) add_executable(UnitDTests ClassATest.cpp ClassBTest.cpp [main.cpp] ) target_link_libraries(UnitDTests PUBLIC UnitD ) add_test( NAME UnitDTests COMMAND UnitDTests )
- 我喜欢将源 (
.cpp
) 和标题 (.h
) 放在同一个文件夹中。这避免了重复的目录层次结构,使维护更容易。对于安装,只过滤头文件是没有问题的(尤其是 CMake)。 - 该目录的作用
UnitD
是稍后允许包含带有#include <UnitD/ClassA.h>
. 此外,在安装本机时,您可以按原样复制目录结构。请注意,您还可以在子目录中组织源文件。 - 我喜欢用
README
文件来总结单元的内容并指定有关它的有用信息。 CMakeLists.txt
可以简单地包含:add_subdirectory(lib) add_subdirectory(test)
lib/CMakeLists.txt
:project(UnitD) set(headers UnitD/ClassA.h UnitD/ClassB.h ) set(sources UnitD/ClassA.cpp UnitD/ClassB.cpp ) add_library(${TARGET_NAME} STATIC ${headers} ${sources}) # INSTALL_INTERFACE: folder to which you will install a directory UnitD containing the headers target_include_directories(UnitD PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> PUBLIC $<INSTALL_INTERFACE:include/SomeDir> ) target_link_libraries(UnitD PUBLIC UnitA PRIVATE UnitC )
在这里,需要注意的是,没有必要告诉CMake的,我们希望包括目录
UnitA
,并UnitC
作为配置这些设备时,这已经指定。此外,PUBLIC
将告诉所有依赖的目标UnitD
它们应该自动包含UnitA
依赖项,而UnitC
此时不需要 (PRIVATE
)。test/CMakeLists.txt
(如果您想使用 GTest,请参见下文):project(UnitDTests) add_executable(UnitDTests ClassATest.cpp ClassBTest.cpp [main.cpp] ) target_link_libraries(UnitDTests PUBLIC UnitD ) add_test( NAME UnitDTests COMMAND UnitDTests )
Using GoogleTest
使用 Google 测试
For Google Test, the easiest is if its source is present in somewhere your source directory, but you don't have to actually add it there yourself. I've been using this projectto download it automatically, and I wrap its usage in a function to make sure that it is downloaded only once, even though we have several test targets.
对于 Google Test,最简单的方法是它的源代码是否存在于您的源代码目录中,但您实际上不必自己将其添加到那里。我一直在使用这个项目来自动下载它,我将它的用法包装在一个函数中以确保它只下载一次,即使我们有几个测试目标。
This CMake function is the following:
这个 CMake 函数如下:
function(import_gtest)
include (DownloadProject)
if (NOT TARGET gmock_main)
include(DownloadProject)
download_project(PROJ googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
UPDATE_DISCONNECTED 1
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent GoogleTest from overriding our compiler/linker options when building with Visual Studio
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
endfunction()
and then, when I want to use it inside one of my test targets, I will add the following lines to the CMakeLists.txt
(this is for the example above, test/CMakeLists.txt
):
然后,当我想在我的测试目标之一中使用它时,我会将以下几行添加到CMakeLists.txt
(这是上面的示例,test/CMakeLists.txt
):
import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)