Doxygen入门教程


Doxygen是API文档生成工具,可以根据代码注释生成文档的工具。支持HTML、CHM、PDF等格式。主要支持C语言、Python语言,其它C语系语言也支持(如C++、Java、C#等)。

Doxygen效果演示

本教程的测试环境

  • Ubuntu 18.04 LTS
  • Doxygen 1.8.13
  • C++
  • Gitlab CI/CD
  • windows

什么是 Doxygen?

Doxygen 是一个将文件的特定注释转化为文档的工具

如何安装 Doxygen?

$ sudo apt install graphviz
$ sudo apt install doxygen

如何使用 Doxygen?

1. 查看你使用的语言 Doxygen 是否默认支持?

Doxygen 默认支持的语言有:C,C++,C#,Objective-C,IDL,Java,VHDL,PHP,Python,Tcl,Fortran 和 D

2. 生成配置文件

$ ## 生成配置文件,默认配置文件名为:Doxyfile
$ doxygen -g   <config-file>
$
$ ## 生成配置文件(不含注释)
$ doxygen -s -g <config-file>
$

3. 修改配置文件

## 常见配置选项

## 设置项目编码,默认为 UTF-8
DOXYFILE_ENCODING = UTF-8

## 设置项目名称
PROJECT_NAME = "project-name"

## 设置项目版本号
PROJECT_NUMBER = "1.0.0"

## 设置项目的描述
PROJECT_BRIEF = "这是项目描述"

## 设置项目的 logo 
PROJECT_LOGO = ""

## 设置输入目录,如果未设置,则在当前目录查找
INPUT = src

## 设置要匹配的输入文件
FILE_PATTERNS = *.cc *.h

## 设置不需要处理的输入目录
EXCLUDE =

## 设置不需要匹配的输入文件
EXCLUDE_PATTERNS =

## 设置输入编码,默认为 UTF-8
INPUT_ENCODING = UTF-8

## 设置是否递归搜索输入目录,默认为 NO
RECURSIVE = NO

## 设置是否提取所有类,函数等(不包括类的私有成员和静态成员),默认为 NO
EXTRACT_ALL = NO

## 设置是否提取类的私有成员,默认为 NO
EXTRACT_PRIVATE = NO

## 设置是否提取类的静态成员,默认为 NO
EXTRACT_STATIC = NO

## 设置文档是否包含源文件,默认为 NO
SOURCE_BROWSER = NO

## 设置是否对每个类都链接到其所在的头文件中,默认值为 YES
VERBATIM_HEADERS = YES

## 设置文档的输出目录
OUTPUT_DIRECTORY = doc

## 设置是否支持 Markdown,默认值为 YES
MARKDOWN_SUPPORT = YES

## 设置文档的主界面
USE_MDFILE_AS_MAINPAGE =

## 设置文档的语言,默认为 English
OUTPUT_LANGUAGE = Chinese         

4. 给代码添加注释
并不是所有的注释都会被收入文档,Doxygen 支持的常用的注释风格有:

/**     注释的内容       */
/*!     注释的内容       */

## 在变量后 注释文件,类,结构体,共同体,枚举成员 或 函数参数
int a; /**<      注释的内容        */
int a; /*!<      注释的内容        */

注意: 这里并不是所有的注释风格,更多注释风格见 官网

Doxygen 常用的注释标记(标记以 / 或 @ 开头表示):

## 添加作者
@author 作者1 作者2

## 添加日期
@date 日期

## 添加文件名
@file 文件名

## 添加简单描述
@brief 简要描述

## 添加详细描述
@details 详细描述

## 添加类信息
@class 类名 类所在的文件 类所在的文件(可包括路径) 

## 添加结构体信息
@class 结构体名 结构体所在的文件 结构体所在的文件(可包括路径)

## 添加宏信息
@enum 宏名

## 添加函数信息
@fn 函数信息

## 添加参数说明
@param [in]   输入参数名 说明
@param [out] 输出参数名 说明

## 添加返回说明
@return 返回说明

## 添加返回特定值说明
@retval 特定值 特定返回值说明

## 添加异常说明
@exception 异常类型 异常说明

## 添加代码
@code
...代码...
@encode

## 添加文件名说明
@headfile 文件名 文件名(可包括路径) 

## 添加版本号
@version 版本号

## 添加计划做的事儿
@todo 计划做的事

## 添加参考 
@see 参加其它

## 添加过时说明
@deprecated 过时说明

## 添加 bug 说明
@bug "bug 说明"

## 添加例子
@example 例子文件名

## 添加警告信息
@warning 警告信息

## 添加开始使用的版本
@since 版本

## 添加测试信息
@test 测试

## 添加主界面信息
@mainpage 标题

## 添加注意事项 
@note 注意事项

## 添加协议信息
@copyright 协议信息

为C/C++添加注释

首先为函数添加注释信息,这是必须要做的。这里有个选择性问题,添加到哪里呢?.c文件?.h文件?

一般来说:

  • .h文件代表模块对外的接口最小信息,面向模块使用者
  • .c文件代表模块的实现代码,面向的是开发者

在实际编程中,事先约定各个模块间的接口,然后将不同的模块分配给不同的开发者,与此同时,测试人员根据接口要求,编写测试代码,这就完全保证了并发编程和白盒测试要求。

这里我们可以看到,文档主要是用来描述接口信息的,所以,我对代码的注释规定如下:

  • 模块对外接口,仅在.h中提供注释信息
  • 模块内部辅助函数,全部用static设为私有函数,同时仅在.c中保留注释信息

当然,您也可以同时为.c .h的接口函数编写两份完全一样的注释信息,但这么做,您会同时维护两份信息,出错的概率会更大些。

确定了注释位置,下一步考虑一个函数需要哪些信息

一般来说,需要函数功能,入口参数,返回值,注意事项,某些时候还需要说明上下文环境,从而保证函数能正确执行

比如这个函数

extern int Dev_PrintInt(int number); 

它的功能就是打印一个整形数据,传入参数为整数,返回的是成功打印的数据长度(字节为单位),同时呢,我们在调用这个函数之前,必须要先初始化Dev设备

ok,这就是所有接口信息,稍微规范一下,就变成了下面的样子

// 函数功能:打印整数
// 入口参数:number为一个整数类型
// 返回结构:返回的是成功打印的数据长度(字节为单位)
// 注意事项:
//          1:在调用本函数前,请确保已经调用Dev_Init初始化设备
//          2:请注意函数返回值,如果该值为0,则说明函数执行失败

extern int Dev_PrintInt(int number); 

用英文来书写呢,则变成下面的样子

//***************************************************************************************
//
// brief  : Print Int number to terimal device.
//
// param  : number is the data you want to print.
// retval : the number of print information, in bytes. return zero indicate print error !
//
// Note:
//      * Be sure you have called \ref Dev_Init function before call this fuction.
//      * Remember to check return value.
//
//***************************************************************************************
extern int Dev_PrintInt(int number);

注释信息写完了,一般来说,函数能达到这种信息程度就ok了,但既然要生成文档,就不得不考虑一个问题

如果你是Doxygen作者,怎么从上面的注释里面提取信息呢,信息那么多,有*号,有各种文字信息。

你可以将所有的注释信息都输出出来,但这么做,等于没有分类整理,同时也包含了杂乱信息,比如一排*

另外一个解决方法是:设置某些特殊字符,比如function表示,一旦检测到这个特殊标记,则认为是接下来
的一行是函数功能描述。但这么做,万一用户的注释里面出现很多个function,你怎么识别哪个是普通文本,
哪个是特殊标记?

也许你会说了,可以采用这种形式啊,恩,这么做是可行的,可以确保识别出来特殊标记

接下来,还有一个问题,我们上面的注释中,有很多*号,仅仅起到美观和格式化的作用,当然不希望在
输出文档中显示这些东西,问题是你怎么识别这些符号,并不显示呢?也许你会说,可以强制规定注释的
格式,不让用户在代码中写很多*,ok,假设用户同意这么做。那接下来呢,如果我希望在代码中写某些话
,但是不希望输出到文档中,比如“XX是2B”等等,你又该怎么做呢?

正向思考遇到问题时,不妨反向考虑,这是谁的问题:是我设计思路的问题还是用户用法的问题?

困难重重,肯定是设计思路的问题

如果设计一个标记符,将普通注释和要生成的文档注释区分开来,就能解决问题了。

Doxygen的用法,说白了,就是为了解决上面提到的两个问题:

怎么区分普通注释和输出注释  
怎么在输出注释里面,识别特殊标记和普通文本  

ok,讲到这里,基本把Doxygen的机制给解释清楚了,如果您还不理解,最简单的方法就是把你假设为Doxygen
作者,重新推演一遍。

下面咱们看看Doxygen怎么解决这两个问题的

区分普通注释和特殊注释

对于C/C++语言来说,注释形式有两种

//
/* */

Doxygen通过在这里增加*/!来作为特殊标记,比如

对于/* */这种注释来说,正常注释为

/*
 * 正常注释
 */

Doxygen在注释第一个*后,设置*!作为标志,如果检测到有这些,
就将接下来的注释作为导出文档来解释

/**
 * 要输出成文档的注释
 */

 或者

/*!
 * 要输出成文档的注释
 */

同时,中间的*号可以省略,像这样

/**
   要输出成文档的注释
 */

 或者

/*!
   要输出成文档的注释
 */

对于//这种类型的注释,Doxygen在第二个/后,增加!/作为区分标志,如果检测到有这些,
就将接下来的注释作为导出文档来解释

/// 要输出成文档的注释

或者

//! 要输出成文档的注释

对于这种呢,有一个潜在的问题,很多时候,我们需要在把注释放到后面,比如下面这种

#define DEV_ON      ((int)(1))      //! Simple device is power on.
#define DEV_OFF     ((int)(0))      //! Simple device is power off.

如果真要这么写的话,Doxygen会把//! Simple device is power on.当做DEV_OFF的注释,这
当然不是我们所希望的! 怎么办呢,只好再加一个特殊标记了,Doxygen针对这种情况,需要在!
再增加一个<标志符,如果检测到这个,则认为这个注释是为前面代码准备的,所以,上面的注释应该
这么写

#define DEV_ON      ((int)(1))      //!< Simple device is power on.
#define DEV_OFF     ((int)(0))      //!< Simple device is power off.

做到这里,Doxygen就可以正确区分普通注释和特殊注释了。

**注:**提到特殊标记,其实吧,编程语言非常常用,比如HTML就是典型的markup语言,一堆一堆的括号,看着就头疼

Doxygen采用\@作为特殊标记符,当在特殊注释里面检测到了特殊标记符,则接下来检测紧跟单词是不是Doxygen
事先规定好的,如果是,则将按照特定的规则来解释紧跟着的注释;如果不是呢,则将\@解释为普通文本,聪明吧

可能有点拗口,下面给你个例子

//***************************************************************************************
//
//! \brief  Print Int number to terimal device.
//!
//! \param  [in] number is the data you want to print.
//! \retval the number of print information, in bytes. return zero indicate print error !.
//!
//! \note
//! * Be sure you have called \ref Dev_Init function before call this fuction.
//! * Remember to check return value.
//
//***************************************************************************************
extern int Dev_PrintInt(int number);

看到了吧,这里的\brief\param都是特殊符号,表示简要描述和参数。万一你小手一抖,把\param
写成了\parame,那就悲剧了,因为Doxygen不认识parame,所以它会把这句话当做是普通文本来处理

其实,上面的\换成@也是ok的,如下所示

//***************************************************************************************
//
//! @brief  Print Int number to terimal device.
//!
//! @param  [in] number is the data you want to print.
//! @retval the number of print information, in bytes. return zero indicate print error !.
//!
//! @note
//! * Be sure you have called \ref Dev_Init function before call this fuction.
//! * Remember to check return value.
//
//***************************************************************************************
extern int Dev_PrintInt(int number);

相信某些玩过ARM芯片的,对这类注释非常熟悉,官方库都是采用Doxygen语法规则注释的

示例

采用Doxygen语法为main.c dev.c dev.h添加注释信息,完成后的效果如下所示:

main.c

//***************************************************************************************
//
//! \file main.c 
//! This is an simple example show developer how to use dev api to print int number.
//!
//! \author    Cedar
//! \version   V1.0
//! \date      2014-03-23
//! \copyright GNU Public License V3.0
//
//***************************************************************************************

#include "dev.h"

#define CNT_MAX  10  //!< The maxium number of print

//! Simple device example.
void DEV_Example(void)
{
	int i = 0;

	Dev_Init();
	
	for (i = 0; i < CNT_MAX; ++i)
	{
		Dev_PrintInt(i);
	}

	Dev_Close();
}

//! Application Entry
int main(void)
{

	DEV_Example();

	return 0;
}

dev.c

//***************************************************************************************
//
//! \file dev.c 
//! the implement of simple device.
//!
//! \author    Cedar
//! \version   V1.0
//! \date      2014-03-23
//! \copyright GNU Public License V3.0
//
//***************************************************************************************

//! Simple device status.
//! 
//! \warning This variable is designed for internal, user \b MUST \b NOT call it.
static int __DevStatus = 0

void Dev_Init(void)
{
	// Print debug information
	printf("Dev Initialize OK!\r\n");
}

int Dev_PrintInt(int number)
{
	printf("Print IntType number: %d\r\n", number);
}

int Dev_StatusCheck(void)
{
	return 	(__DevStatus);
}

void Dev_Close(void)
{
	printf("Dev Close OK!\r\n");
}

dev.h

//***************************************************************************************
//
//! \file dev.h
//!  Simple device user API.
//!
//! \author    Cedar
//! \version   V1.0
//! \date      2014-03-23
//! \copyright GNU Public License V3.0
//
//***************************************************************************************

#include <stdio.h>


//***************************************************************************************
//
//! \addtogroup Dev_Status  Simple device status information.
//! @{
//
//***************************************************************************************

#define DEV_ON      ((int)(1))      //!< Simple device is power on.
#define DEV_OFF     ((int)(0))      //!< Simple device is power off.

//***************************************************************************************
//
//! @}
//
//***************************************************************************************


//***************************************************************************************
//
//! \addtogroup Dev_API  Simple device APIs list.
//! @{
//
//***************************************************************************************

//***************************************************************************************
//
//! \brief  Initialize simple device.
//!
//! \param  none.
//! \retval none.
//!
//! \note   This function \b MUST be called first before others function.
//
//***************************************************************************************
extern void Dev_Init(void);

//***************************************************************************************
//
//! \brief  Print Int number to terimal device.
//!
//! \param  [in] number is the data you want to print.
//! \retval the number of print information, in bytes. return zero indicate print error !.
//!
//! \note
//! * Be sure you have called \ref Dev_Init function before call this fuction.
//! * Remember to check return value.
//
//***************************************************************************************
extern int Dev_PrintInt(int number);

//***************************************************************************************
//
//! \brief  Check simple device status information.
//!
//! \param  none.
//! \retval status information of simple device, which can be one of the following value:\n
//!  - \ref DEV_ON
//!  - \ref DEV_OFF
//!  \n More information, please reference \ref Dev_Status.
//
//***************************************************************************************
extern int Dev_StatusCheck(void);

//***************************************************************************************
//
//! \brief  Close simple device.
//!
//! \param  none.
//! \retval none.
//
//***************************************************************************************
extern void Dev_Close(void);

//***************************************************************************************
//
//! @}
//
//***************************************************************************************

//***************************************************************************************
//
//! \example main.c
//!  Show how to use simple device to print int number.
//
//***************************************************************************************

4. 生成文档

$ doxygen <config-file>

Win 图形界面使用

第1章 安装

在Linux下可以通过apt install doxygen安装命令行工具,然后用apt install doxygen-gui安装图形界面。对Linux用户来说,命令行工具可以通过doxygen命令运行,而图形界面可以通过doxywizard命令运行。

而Windows用户可以在这里下载,安装完毕后,直接双击就能运行图形界面。

1.1 基本使用

图形工具的基本使用如下图所示,有非常多的配置选项,这里我们只填入必要的配置,其它配置都用默认值。

doxywizard使用步骤

doxywizard使用步骤

我们的工作目录如下:

.
├── out
└── src
    └── math.h

其中math.h代码如下:

/*! \file math.h */

/*!
    用于求一个角度的sin值,输入是字符串以便同时支持弧度制和角度制表示
    \li 弧度制用pi表示,例如:2pi表示一圈、0.5pi表示直角
    \li 角度制用d结尾,例如:360d表示一圈、90d表示直角
    \li 输入也可以是数值,例如:输入3.14159大约表示180度

    \param a 用弧度制或角度制表示都行,字符串必须用'\0'表示结束
    \param[out] res 是输出参数,用于保存sin运算的结果

    \return 错误码,0表示成功,其它表示失败

    \todo 在xxx的情况下存在BUG,预计下一版本修复
*/
int sin(char *a, double *res);

Doxygen生成的HTML会放到out目录下,生成的HTML如图1-3所示。

HTML界面

1.2 保存配置

在1.1节中我们配置了一些选项,也成功生成了HTML文档。我们希望下次代码改动后能够继续沿用上次配置,那么我们可以把这些配置保存成Doxyfile文件,见图1-4。

保存Doxyfile配置文件

1.3 命令行运行Doxygen

有了配置文件后我们完全可以通过命令行来生成API文档,假设配置文件名为Doxyfile,那么我们只需要执行doxygen /path/to/Doxyfile即可生成API文档。

通过命令行生成文档有许多好处,其中最主要的好处就是:能够集成到持续集成之类的自动化系统中。

第2章 为代码编写注释

2.1 什么样的注释会被Doxygen识别?

Doxygen能识别这几种风格的注释:

/**
 * ... text ...
 */

/*!
 * ... text ...
 */

///
/// ... text ...
///

//!
//!... text ...
//!

文件的开头必须有文件注释,否则该文件不会被识别:

/*! \file math.h */

2.2 注释怎么写

这个自己看官网例子体会吧。

第3章 为其它编程语言生成注释

Doxygen主要支持C语言,其它语法跟C差不多的语言(如:C++/C#/PHP/Java)也能够支持,我们称这类语言为「C语系语言」。而哪些跟C语法差异较大的语言叫做「非C语系语言」。

对于大多非C语系语言,Doxygen都是支持的,Doxygen原生支持这些语言:IDL、Java、Javascript、C#、C、C++、D、PHP、Objective-C、Python、Fortran、VHDL。

万一项目需要的语言(例如:Lua)Doxygen官方并不支持,那么只能自行编写「第三方语言扩展」来支持了。

3.1 Doxygen官方支持的语言

见图3-1,文件名符合FILE_PATTERNS都会被处理。其中包括了.c.h.py等等。

img

如果我们的扩展名并不在FILE_PATTERNS内,那么可以加上去。例如我们项目下的所有.ccc文件,其实是C语言代码(这很奇葩,举个例子而已)。那我们可以编辑Doxyfile配置文件满足这一需求,需要2个步骤。

(1) 在FILE_PATTERNS中添加*.ccc,如图3-2

img

(2) 在EXTENSION_MAPPING中添加映射规则ccc=C,如图3-3。语法是ext=language,其中language可以取的值有:IDL、Java、Javascript、C#、C、C++、D、PHP、Objective-C、Python、Fortran、VHDL。

img

3.2 Doxygen官方不支持的语言

以Lua语言为例,它的代码是长这样的:

-- \file lmath.h

--[[
    用于求一个角度的sin值,输入是字符串以便同时支持弧度制和角度制表示
    \li 弧度制用pi表示,例如:2pi表示一圈、0.5pi表示直角
    \li 角度制用d结尾,例如:360d表示一圈、90d表示直角
    \li 输入也可以是数值,例如:输入3.14159大约表示180度

    \param a 字符串类型,表示角度,用弧度制或角度制表示都行

    \return 返回sin运算的结果

    \todo 在xxx的情况下存在BUG,预计下一版本修复
--]]
function sin(a)
    return 1.123
end

可以看到Lua的语法既不像C也不像Python。本节以Lua为例,介绍如何为Doxygen编写Lua语言扩展。
好吧,大多数人没有这种需求,这里就不介绍了。

第4章 定制Doxygen的输出

4.1 定制页面样式

Doxygen输出的默认HTML比较难看,如图4-1。

img

如果嫌生成的HTML不好看,可以自定义HTML页面头部、尾部以及页面整体CSS样式表。
(1) 生成默认的风格的配置文件,敲这个命令:doxygen -w html header.html footer.html customdoxygen.css,可以生成header.htmlfooter.htmlcustomdoxygen.css
(2) 根据自己的需求修改这三个文件。
(3) 配置HTML_HEADERHTML_FOOTERHTML_STYLESHEET指向修改后的文件,如图4-2。

img

Doxygen默认的页面主色调大约是天蓝色的,可以通过HTML_COLORSTYLE_HUEHTML_COLORSTYLE_SATHTML_COLORSTYLE_GAMMA修改主色调,这3个配置分别对应色相、饱和度、Gamma校正,见图4-3。如果不太懂色相、饱和度是啥意思,请自行百度「色彩模式」或参考Photoshop相关教程。

img

经过图4-3的修改,页面的主色调变为图4-4的样子。

img

4.2 导航栏

Doxygen中「导航栏」有两种展示方式:Treeview和Index,分别是竖向和横向的,如图4-5。

img

可以配置DISABLE_INDEXGENERATE_TREEVIEW来控制是否显示它们,如图4-6。

img

4.3 自定义「导航栏」的目录结构

我们已经知道Doxygen中「导航栏」有Treeview和Index两种了。这节介绍如何定制导航栏的目录结构。这需要三个步骤。
(1) 执行doxygen -l,生成DoxygenLayout.xml文件
(2) 编辑DoxygenLayout.xml文件,修改其中的布局
(3) 修改LAYOUT_FILE配置,使其指向DoxygenLayout.xml文件,如图4-7
(4) 运行Doxygen

img

那么如何修改XML文件呢?默认的DoxygenLayout.xml代码如下:

<doxygenlayout version="1.0">
  <navindex>
    <tab type="mainpage" visible="yes" title=""/>
    <tab type="pages" visible="yes" title="" intro=""/>
    <tab type="modules" visible="yes" title="" intro=""/>
    <tab type="namespaces" visible="yes" title="">
      <tab type="namespacelist" visible="yes" title="" intro=""/>
      <tab type="namespacemembers" visible="yes" title="" intro=""/>
    </tab>
    <tab type="classes" visible="yes" title="">
      <tab type="classlist" visible="yes" title="" intro=""/>
      <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> 
      <tab type="hierarchy" visible="yes" title="" intro=""/>
      <tab type="classmembers" visible="yes" title="" intro=""/>
    </tab>
    <tab type="files" visible="yes" title="">
      <tab type="filelist" visible="yes" title="" intro=""/>
      <tab type="globals" visible="yes" title="" intro=""/>
    </tab>
    <tab type="examples" visible="yes" title="" intro=""/>  
  </navindex>
</doxygenlayout>

XML对应了导航栏的目录树结构,我们通过该文件改变布局。标签的type属性取值除了上面列出的这些预定义值以外,还可以是type="user"type="usergroup",我们只能通过这两个type自定义布局,例如下面这段代码,生成的效果如图4-8:

<doxygenlayout version="1.0">
  <navindex>
    <tab type="usergroup" visible="yes" title="友情链接(演示如何外链)">
      <tab type="user" visible="yes" title="百度" url="http://www.baidu.com" />
      <tab type="user" visible="yes" title="163" url="http://www.163.com" />
    </tab>
    <tab type="usergroup" visible="yes" title="数学库(演示如何链接文件)">
      <tab type="user" visible="yes" url="@ref math.h" title="math" />
      <tab type="user" visible="yes" url="@ref math2.h" title="math2" />
    </tab>
    <tab type="usergroup" visible="yes" title="三角函数(演示链接函数、结构体)">
      <tab type="user" visible="yes" url="@ref sin" title="sin" />
      <tab type="user" visible="yes" url="@ref sin2" title="sin2" />
    </tab>
  </navindex>
</doxygenlayout>

img

4.4 完全自定义

如果Doxygen输出的界面实在不入你的法眼,4.1~4.3介绍的定制化功能也不能彻底满足你的需求。那么你需要根据Doxygen输出的XML数据自行生成界面了。
(1) 将GENERATE_XML配置为YES
(2) 去输出目录寻找生成的XML文件,XML文件包括了函数信息、注释信息等
(3) 自己写程序读取XML文件,并生成漂亮的文档

第5章 Markdown支持

待补充完善

Markdown在工业界是非常流行的文档格式,文件名以.md结尾,其简洁直观的语法深受广大程序员喜爱。对Markdown本身的介绍超出了本文范围,本章介绍Doxygen对Markdown的支持。

5.1 为.md文件生成文档

5.2 在代码注释中使用Markdown语法

第6章 搜索功能

Gitlab CI/CD使用

https://gitlab.com/pages/doxygen

结合Docker和Gitlab CI/CD使用案例

Gitlab Doxygen运行效果如下

参考资源


文章作者: 夜法之书
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 夜法之书 !
评论
 上一篇

阅读全文

常用Linux Bash命令教程
常用Linux Bash命令教程 常用Linux Bash命令教程
常用linux命令介绍。入门Linux必学的命令行,熟练掌握后,效率远超Windows GUI操作。重要的是,可以方便的做自动化处理!
2022-05-28
下一篇 

阅读全文

自建全套开源Devops开发系统
自建全套开源Devops开发系统 自建全套开源Devops开发系统
国内的Devops云平台动辄封号,删库,屏蔽,还多次发生数据泄密,项目被云平台克隆事件,国内云平台devops已死!数据安全没法保证,自建私有Devops平台才靠谱。这里采用开源项目建立一个完善的低成本Devops系统,基本运行良好!
2022-05-09
  目录