一个炫酷的命令行程序它到底需要什么?

  • 构建系统
  • 命令行参数解析
  • 日志系统
  • 代码文档系统

第三方库可以用cmake管理,自从qt官方构建系统转到cmake之后,感觉cmake基本上已经成为c++项目的事实构建系统了。 命令行参数解析和日志系统可以使用gflags和glog,代码文档系统自然是通用的doxygen。

cmake

基本上可以把makefile淘汰掉了,makefile最致命的一点就是无法自动构建依赖。在c和c++中,源文件的头文件依赖如果使用makefile构建,要么就手动管理依赖(要命),要么就写个又长又臭的makefile来自动构建(还是要命)。在简单地学会cmake之后,就放心大胆地跟makefile说拜拜吧。

构建工程

有一些必须添加的首行比如指定cmake版本,添加源文件。

cmake_minimum_required(VERSION 3.20)
project(cmdline)
add_executable(${PROJECT_NAME} main.c)

还有就是引入依赖,这个工程我是基于glib做的。glib提供命令行解析、日志系统、单元测试等功能。

find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB glib-2.0 REQUIRED)
add_definitions(-DG_LOG_USE_STRUCTURED)
include_directories(${GLIB_INCLUDE_DIRS})

文档生成,引入Doxygen生成文档。

find_package(Doxygen)
if (DOXYGEN_FOUND)
        message("Doxygen build start")
        doxygen_add_docs(docs ${PROJECT_SOURCE_DIR} ALL)
endif()

命令行参数解析

命令行参数解析最常用的是posix标准getopt或者gnu的getopt,qt和glib都有自己的命令行解析工具。 我使用的是glib提供的命令行解析,简单用法跟getopt差不多。不过glib这个东西还扩展了一下用法, 有group这个概念,因为文档不详细我也没仔细研究。

static GOptionEntry entries[] = {
	{"ppid", 'p', 0, G_OPTION_ARG_STRING, &ppid, "Product id in apple mfi website", DEFAULT_PPID},
	{"logfile", 'l', 0, G_OPTION_ARG_STRING, &logfile, "The path to log file", DEFAULT_LOG_FILE},
	{NULL}
};

gboolean ret = FALSE;
GOptionContext *context = g_option_context_new("device-uuid");
/* setting parser */
g_option_context_add_main_entries(context, entries, NULL);
g_option_context_set_summary(context, "This program use to enable and disable apple token");
/* parse it */
ret = g_option_context_parse(context, &argc, &argv, NULL);
if (!ret)
	g_error("parse err");

解析完成之后,之前定义的变量就已经被改变,可以直接使用。

日志系统

glib的文档的确是有些少,如果需要找用法的话,把代码仓库克隆下来直接学习单元测试的代码比较好。 默认设置是不会输出g_info和g_debug和g_critical的,必须定义环境变量G_MESSAGE_DEBUG=all才行。 所以如果没有改变日志系统的全局回调函数,建议还是使用g_message, g_warning和g_error作为代替。

在这个工程中,日志我统一追加到日志文件的后面。debug由宏NDEBUG控制而且只输出到控制台, error不但输出到控制台还会输出到日志文件中。具体实现可以看main.c,下面贴出设置回调的代码。

static GLogWriterOutput log_write(
		GLogLevelFlags log_level,
		const GLogField *fields,
		gsize n_fields,
		gpointer user_data)
{ ... }

static void set_log_path(const char *log_path)
{
	FILE *logfile = fopen(log_path, "a");
	if (logfile == NULL)
		g_error("open log file err: %s\n", strerror(errno));

	g_log_set_writer_func(log_write, (void *)logfile, NULL);
}

FILE *logfile = fopen(log_path, "a");
if (logfile == NULL)
	g_error("open log file err: %s\n", strerror(errno));
g_log_set_writer_func(log_write, (void *)logfile, NULL);

代码文档系统

直接从代码里生成文档是一件非常爽的事情,doxygen进行注释需要注意三个方面。

  • 配置文件
  • 文件头标识
  • 函数注释和变量注释

配置文件

直接命令doxygen -g生成默认配置文件DoxyFile,具体的设置可以参考官网。 在这个配置中我只改了一个变量,EXTRACT_STATIC=YES对static成员也生成注释。 一般的功能都可以用标识来实现。生成的话在上面的cmake配置文件里有写,当然直接生成也可以。

我在工程中是直接在从CMakeLists.txt中配置Doxygen的,所以并不需要Doxygen文件。

文件头标识

在这个文件(README.md)头加上/** \mainpage这一行就能把这个文件作为生成文档的主页面, proxygen直接支持markdown语法,写起来还是挺爽的。

源代码文件头要加上一些标识符,指定对这个源文件进行文档生成。\file是必须有的, 具体可以参考官网

/**
 * \file main.c
 * \author Ray Ruan(a1173522112@163.com)
 * \date 2021-10-13
 */

注释

注释主要分两种,变量注释和一般注释。下面我展示我的用法,具体还是要看官网

int a; /**< variable a */
/**
 * \brief write buffer to somewhere
 * \details details of this function
 * \param buf buffer
 * \return none
 */
 void write(char buf[]);

## 工程代码 工程代码我丢到了github上,有兴趣的话可以参考一下。