本节描述了OpenFOAM源代码的组织以及与开发OpenFOAM相关的工作流程。在进行代码开发时,代码应组织为两层:库代码及应用程序代码。库代码可以包含实现各种可重用部件或组件的单个或多个库。这完全取决于库的设计者以及库的构建方式。有时,库级代码被称为应用程序逻辑。库代码在设计上并不特定于单个应用程序:它被许多可执行应用程序重用。库将包含函数或类的声明及其实现。该库通常被编译成所谓的“目标代码”,以节省编译时间。然后,当链接器程序执行库对象代码时,它将链接到应用程序。请注意,虽然库包含已编译代码,但它们不能像可执行应用程序那样在命令行中执行。

另一方面,应用程序代码使用库代码来组装更高级别的功能。OpenFOAM的流动求解器就是一个很好的例子,因为它结合了单独的库来处理概念上不同的任务,如磁盘I/O、网格处理、离散等。求解器的开发者不必关心各个库背后的逻辑,从而可以集中精力开发求解器。

当源代码被组织到应用程序层和库层时,它通常更容易扩展,并且可以更容易地与其他人共享。由于OpenFOAM遵循这种方法,顶级目录$WM_PROJECT_DIR包含两个子目录:applicationssrc。其中src文件夹存储各种库,而applications文件夹存储使用这些库的可执行应用程序。

在开发过程中,程序员可以选择两种代码组织方式:在OpenFOAM目录结构中编程或在单独的目录结构中编程。乍一看,在OpenFOAM结构中编程很有意义,因为在目录树中保持相似的代码非常接近是合乎逻辑的。然而不幸的是,当使用版本控制系统(VCS)与其他人协作时,这种“主结构”的开发方法会很快导致问题。版本控制系统对于协作编程是绝对必要的。即使开发人员独自工作,VCS也会显著加快开发速度和开发过程的安全性,因为它可以直接调查其他想法。即使版本控制系统使用得当,与其他人共享位于OpenFOAM主存储库中的自定义代码也可能会出现问题,原因如下:

  • 对于属于项目的文件没有明确的概述
  • 教程和测试用例放在单独的目录结构中
  • 处理未与OpenFOAM一起分发的其他依赖项可能会导致变更集成问题
  • 与其他人合作需要访问完整的OpenFOAM版本

最后一点使协作工作变得困难,因为创建整个发布存储库的克隆使得任何人都有必要克隆整个OpenFOAM平台,以便在通常更小的新项目上进行协作。此外,还有一些情况是,这些项目没有与公众共享:它们是由公司的研究部门开发的,或者功能还不够成熟,无法发布。在这种情况下,当项目从一开始就不需要与OpenFOAM版本集成时,将库和应用程序代码捆绑在一个单独的存储库中,可以直接编译,这使得个人和协作开发更加容易。在后一点上,当项目证明质量更高时,可以将其集成到其中一个OpenFOAM发布项目中。现在有多种VCS托管服务可用。这些服务提供了高级web界面,允许用户使用bug跟踪、为每个项目托管wiki以及其他工具,这些工具使处理此类项目变得更加容易。其中最受欢迎的有gitlab、github和bitbucket等。

6.1.1 一个新OpenFOAM项目的目录结构

将相同的OpenFOAM组织结构应用于定制的OpenFOAM项目,可以简化协作工作和将来与OpenFOAM的集成。如果代码以这种方式组织,那么对于熟悉OpenFOAM目录结构的用户来说,目录的结构将不言自明。维护统一的目录结构也是间接记录代码的基本方法。要检查示例目录组织,请考虑本书的示例代码存储库及其组织方式。

图6.1 示例代码的目录结构

如图6.1所示,application目录是存储应用程序的地方。在application目录中存在以下子目录:solvers、test和utilities。etc目录用于配置代码的编译。每个库的src文件夹的组织结构都不同。当类提取和封装不同抽象之间的常见行为时,分层代码组织也会将不同的类实现集合分离到单独的可链接库中。在引入更改时,组织和分离库类别可以减小已编译应用程序代码的大小,从而加快编译速度。

README文件通常位于代码存储库的顶部目录中,此文件非常有用,因为它是用户开始使用新代码时读取的第一个文件。通常可以在那里找到对项目背景及其最重要应用程序的一般描述,以及到外部文档来源和论坛的最新链接。可以使用Doxygen文档系统生成代码的本地文档,该系统使用doxyfile指定文件以及涉及生成的HTML文档的外观的详细信息。

6.1.2 自动化安装

除了图6.1中的目录组织之外,还应启用简化的自动构建过程。OpenFOAM使用自己的构建系统wmake,该系统利用各种环境变量来自动编译和链接库和应用程序代码。同样的方法可以应用于自定义代码存储库,并针对提供的示例代码存储库实现。示例代码库的bashrc配置脚本如下所示:

#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export PRIMER_EXAMPLES=${DIR%%/etc*}
export PRIMER_EXAMPLES_SRC=$PRIMER_EXAMPLES/src
export PATH=$PRIMER_EXAMPLES_SRC/scripts:$PATH

bash配置脚本设置名为$PRIMER_EXAMPLES的路径变量,该变量是代码存储库主文件夹的路径变量。PATH变量需要扩展,因为示例代码库包含OpenFOAM中的脚本,这些脚本位于src/scripts中。否则无法从文件系统中的任何位置调用这些脚本。通过对项目路径使用不同的变量,可以在另一个存储库中重用此处的结构和配置。代码存储库的应用程序和库依赖变量$PRIMER_EXAMPLES来查找在编译之前包含的头文件的目录。

根目录中的编译脚本AllwmakeAllwclean分别用于编译和清理项目二进制文件。此外类似的脚本放在srcapplications子目录中,这样就可以只编译库或应用程序。用于在src目录中构建库的Allwmake脚本的示例内容如以下脚本所示:

#!/bin/sh
cd ${0%/*} || exit 1 # run from this directory
wmakeLnInclude .
wmake exampleLibrary

在wmakelinclude中,脚本递归搜索当前目录,查找所有OpenFOAM源文件,并在src/lnInclude目录中创建指向这些文件的符号链接。这大大简化了构建过程的配置,因为库的所有头文件都不会分散在不同的子目录中;编译时所需的所有头文件都链接到一个位置lnInclude。一旦定义了存储库文件夹的绝对路径($PRIMER_EXAMPLESetc/bashrc 脚本定义),包含类声明的头文件就依赖于存储在src/lnInclude目录中的所有源文件的符号链接。要编译的库的名称(在本例中为exampleLibrary)作为一个参数传递给wmake脚本,该脚本确保构建过程将生成一个可动态链接的库。

OpenFOAM的应用程序代码通常存储在以应用程序命名的目录中。示例应用程序目录的内容如图6.2所示。applicationName.C是应用程序的源文件。wmake构建系统使用filesoptions文件来编译应用程序代码。

文件Make/files中的内容很简单,如下所示:

applicationName.C
EXE = $(FOAM_USER_APPBIN)/applicationName

Make/files文件列出了要编译的C文件以及包含已编译代码的二进制文件的名称和位置。应用程序安装目标目录设置为$FOAM_USER_ APPBIN,以避免自定义应用程序污染OpenFOAM系统应用程序目录$FOAM\U APPBIN。使用$FOAM_USER_APPBIN将二进制文件存储在一个文件夹中,该文件夹是$HOME的子文件夹:这避免了在计算机上构建自定义项目时需要root权限,其中一个OpenFOAM安装可能会在所有具有非root权限的用户之间共享,例如集群。Make/options文件包含包含声明(*.H)文件的所有目录,即所谓的包含目录以及包含库的目录(-L)。options文件内容如下所示:

EXE_INC = \
        -I$(LIB_SRC)/finiteVolume/lnInclude \
        -I$(LIB_SRC)/meshTools/lnInclude \
        -I$(PRIMER_EXAMPLES_SRC)/lnInclude

EXE_LIBS = \
        -L$(FOAM_USER_LIBBIN) \
        -lfiniteVolume \
        -lmeshTools \
        -lexampleLibrary

options文件显示,自定义应用程序applicationName依赖于存储库变量$PRIMER_EXAMPLES_SRC和OpenFOAM生成的lnInclude目录来定位所需的头文件。此外,应用程序将链接到库exampleLibrary,并使用其中包含的所需功能。

提示:拥有自定义项目目录结构的关键步骤是准备bashrc配置脚本。依靠该脚本设置的变量来定位自定义项目的头文件和库,将自定义项目与OpenFOAM平台分离。

这种配置是使用自定义代码进行工作和编程的简单方法。以下列表是上述工作流成中步骤的摘要:

  • 在终端运行命令source ./etc/bashrc以设置环境变量$PRIMER_EXAMPLES
  • 如果要永久设置环境变量,可以将source/path/to/code/directory/etc/bashrc添加到shell的启动脚本中
  • 在顶层代码目录中运行./Allwmake编译所有的库与应用程序
  • 在顶层代码目录中运行./Allwclean清除所有的二进制文件
  • 将库(例如libraryName)添加到存储库时,编辑src/Allwmake并添加wmake libraryName以进行编译,以及在src/Allwclean中添加wclean libraryName以清理新库代码的二进制文件
  • 如果为应用程序使用示例代码存储库中的标准目录,则将在不编辑编译或清理脚本的情况下对其进行编译和清理。