zl程序教程

您现在的位置是:首页 >  工具

当前栏目

CMakeLists Guide笔记

笔记 Guide
2023-09-11 14:18:28 时间

CMakeLists.txt

1 cmake常用命令汇总
【1】指定 cmake 的最小版本

cmake_minimum_required(VERSION 3.5)

这行命令是可选的,我们可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。
【2】设置项目名称

project(demo)

这个命令不是强制性的,但最好都加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。
【3】设置编译类型

add_executable(demo demo.cpp) # 生成可执行文件
add_library(common STATIC util.cpp) # 生成静态库
add_library(common SHARED util.cpp) # 生成动态库或共享库

add_library 默认生成是静态库,通过以上命令生成文件名字,
在 Linux 下是:
demo
libcommon.a
libcommon.so
在 Windows 下是:
demo.exe
common.lib
common.dll
【4】指定编译包含的源文件
【4.1】明确指定包含哪些源文件
add_library(demo demo.cpp test.cpp util.cpp)
【4.2】搜索所有的 cpp 文件

aux_source_directory(dir VAR) 发现一个目录下所有的源代码文件并将列表存储在一个变量中。
aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件
add_library(demo ${SRC_LIST})

【4.3】 自定义搜索规则

file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${SRC_LIST})

或者

file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

或者

aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

【5】 查找指定的库文件
find_library(VAR name path)查找到指定的预编译库,并将它的路径存储在变量中。
默认的搜索路径为cmake 包含的系统库,因此如果是 NDK 的公共库只需要指定库的 name 即可。

find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log)

类似的命令还有 find_file()、find_path()、find_program()、find_package()。

【6】设置包含的目录

include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
)

Linux下还可以通过如下方式设置包含的目录

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")

【7】设置链接库搜索目录

link_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs)

Linux下还可以通过如下方式设置包含的目录

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")

【8】设置 target 需要链接的库
target_link_libraries( #目标库
demo
#目标库需要链接的库
# log-lib 是上面 find_library 指定的变量名
${log-lib} )
在 Windows下,系统会根据链接库目录,搜索xxx.lib 文件,Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)。
【8.1】指定链接动态库或静态库

target_link_libraries(demo libface.a) # 链接libface.a
target_link_libraries(demo libface.so) # 链接libface.so

【8.2】指定全路径

target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)

【8.3】指定链接多个库

target_link_libraries(demo
${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a
boost_system.a
boost_thread
pthread)

【9】设置变量
【9.1】set直接设置变量的值

set(SRC_LIST main.cpp test.cpp)
add_executable(demo ${SRC_LIST})

【9.2】set追加设置变量的值

set(SRC_LIST main.cpp)
set(SRC_LIST ${SRC_LIST} test.cpp)
add_executable(demo ${SRC_LIST})

【9.3】list 追加或者删除变量的值

set(SRC_LIST main.cpp)
list(APPEND SRC_LIST test.cpp)
list(REMOVE_ITEM SRC_LIST main.cpp)
add_executable(demo ${SRC_LIST})

【10】条件控制
【10.1】if…elseif…else…endif
逻辑判断和比较:

if (expression):expression 不为空(0,N,NO,OFF,FALSE,NOTFOUND)时为真
if (not exp):与上面相反
if (var1 AND var2)
if (var1 OR var2)
if (COMMAND cmd):如果 cmd 确实是命令并可调用为真
if (EXISTS dir) if (EXISTS file):如果目录或文件存在为真
if (file1 IS_NEWER_THAN file2):当file1比file2新,或 file1/file2中有一个不存在时为真,文件名需使用全路径
if (IS_DIRECTORY dir):当 dir 是目录时为真
if (DEFINED var):如果变量被定义为真
if (var MATCHES regex):给定的变量或者字符串能够匹配正则表达式regex时为真,此处 var 可以用 var 名,也可以用 ${var}
if (string MATCHES regex)

数字比较:

if (variable LESS number):LESS 小于
if (string LESS number)
if (variable GREATER number):GREATER 大于
if (string GREATER number)
if (variable EQUAL number):EQUAL 等于
if (string EQUAL number)

字母表顺序比较:

if (variable STRLESS string)
if (string STRLESS string)
if (variable STRGREATER string)
if (string STRGREATER string)
if (variable STREQUAL string)
if (string STREQUAL string)

示例:

if(MSVC)
    set(LINK_LIBS common)
else()
    set(boost_thread boost_log.a boost_system.a)
endif()
target_link_libraries(demo ${LINK_LIBS})

或者

if(UNIX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -g")
else()
    add_definitions(-D_SCL_SECURE_NO_WARNINGS
    -D_CRT_SECURE_NO_WARNINGS
    -D_WIN32_WINNT=0x601
    -D_WINSOCK_DEPRECATED_NO_WARNINGS)
endif()
 
if(${CMAKE_BUILD_TYPE} MATCHES "debug")
    ...
else()
    ...
endif()

【10.2】while…endwhile

while(condition)
...
endwhile()

【10.3】 foreach…endforeach

foreach(loop_var RANGE start stop [step])
...
endforeach(loop_var)

start 表示起始数,stop表示终止数,step表示步长,示例:

foreach(i RANGE 1 9 2)
message(${i})
endforeach(i)

输出:13579

【11】打印信息
message:可以通过message来输出变量的值

message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败
message(${PROJECT_SOURCE_DIR})

【12】包含其它 cmake 文件

include(./common.cmake) # 指定包含文件的全路径
include(def) # 在搜索路径中搜索def.cmake文件
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径

【13】常用变量
【13.1】预定义变量
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置
【13.2】环境变量
使用环境变量 KaTeX parse error: Expected 'EOF', got '#' at position 39: …V{Name} value) #̲ 这里没有“”符号
【13.3】系统信息
­CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3
­CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4
­CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1
­CMAKE_SYSTEM:系统名称,比如 Linux-­2.6.22
­CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux
­CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22
­CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686
­UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin
­WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwin
【13.4】主要开关选项
BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加
CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加
add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔

【14】宏和函数中ARGV ARGN参数的区别
cmake中的宏(marco)和函数(function)都支持动态参数
变量ARGC:记录的是传入的参数个数
变量ARGV0,ARGV1,…顺序代表传入的参数
变量ARGV:包含所有传入参数列表
变量ARGN:包含传入的参数列表,但不是所有参数,而是marco和function声明的参数之后的所有传入参数
示例:
#定义一个宏,显式声明两个参数hello,world

macro(argn_test hello world)
	message(STATUS ARGC=${ARGC})
	message(STATUS ARGV=${ARGV})
	message(STATUS ARGN=${ARGN})
	message(STATUS ARGV0=${ARGV0})
	message(STATUS ARGV1=${ARGV1})
	message(STATUS ARGV2=${ARGV2})
	message(STATUS ARGV3=${ARGV3})
endmacro(argn_test)

#调用时显示传入两个参数
argn_test(param0 param1)
输出结果:
– ARGC=2
– ARGV=param0param1
– ARGN=
– ARGV0=param0
– ARGV1=param1
– ARGV2=
– ARGV3=
#调用时显示传入四个参数
argn_test(param0 param1 param3 param4)
输出结果:
– ARGC=4
– ARGV=param0param1param3param4
– ARGN=param3param4
– ARGV0=param0
– ARGV1=param1
– ARGV2=param3
– ARGV3=param4

2 示例
调试环境:ubuntu16.04
代码示例:Linux-C/CMakeLists/code/
例一 单个源文件 main.c
例二 ==>分解成多个 main.c hello.h hello.c
例三 ==>先生成一个静态库,链接该库
例四 ==>将源文件放置到不同的目录
例五 ==>控制生成的程序和库所在的目录
例六 ==>使用动态库而不是静态库
例一
一个经典的C程序,如何用cmake来进行构建程序呢?

//main.c
#include <stdio.h>
int main()
{
    printf("Hello World!\n");
    return 0;
}

编写一个 CMakeLists.txt 文件(可看做cmake的工程文件):

project(HELLO)
set(SRC_LIST main.c)
add_executable(hello ${SRC_LIST})

注意:是CMakeLists.txt而不是CMakeList.txt
然后,建立一个任意目录(比如本目录下创建一个build子目录),在该build目录下调用cmake
注意:为了简单起见,我们从一开始就采用cmake的 out-of-source 方式来构建(即生成中间产物与源代码分离),并始终坚持这种方法,这也就是此处为什么单独创建一个目录,然后在该目录下执行 cmake 的原因
example_1/build$ cmake … -G “Unix Makefiles”
example_1/build$ make
Scanning dependencies of target hello
[ 50%] Building C object CMakeFiles/hello.dir/main.c.o
[100%] Linking C executable hello
[100%] Built target hello

目录结构:

.
├── build
│   └── hello
├── CMakeLists.txt
└── main.c
CMakeLists.txt

第一行 project 不是强制性的,但最好始终都加上。这一行会引入两个变量
HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR
同时,cmake自动定义了两个等价的变量

PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR
因为是out-of-source方式构建,所以我们要时刻区分这两个变量对应的目录

可以通过message来输出变量的值
message(${PROJECT_SOURCE_DIR})
set 命令用来设置变量
add_exectuable 告诉工程生成一个可执行文件。
add_library 则告诉生成一个库文件。
注意:CMakeLists.txt 文件中,命令名字是不区分大小写的,而参数和变量是大小写相关的。
cmake命令
cmake 命令后跟一个路径(…),用来指出 CMakeLists.txt 所在的位置。
由于系统中可能有多套构建环境,我们可以通过-G来制定生成哪种工程文件,通过 cmake -h可得到详细信息。
要显示执行构建过程中详细的信息(比如为了得到更详细的出错信息),可以在CMakeLists.txt内加入:
SET(CMAKE_VERBOSE_MAKEFILE on )
或者执行make时
$ make VERBOSE=1
或者
$ export VERBOSE=1
$ make

例二
一个源文件的例子一似乎没什么意思,拆成3个文件再试试看:代码目录:Linux-C/CMakeLists/code/example_2

hello.h 头文件

#ifndef __HELLO_H__
#define __HELLO_H__
void hello(const char * fmt);
#endif

hello.c
#include "hello.h"
#include <stdio.h>
void hello(const char * fmt)
{
	printf("[hello] %s\n", fmt);
}

main.c
//main.c
#include "hello.h"
int main()
{
    hello("Hello World!\n");
    return 0;
}

编译命令:
example_2/build$ cmake … -G “Unix Makefiles”
example_2/build$ make
目录结构:

.
├── build
│   └── hello
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

例三
接前面的例子,我们将 hello.c 生成一个库,然后再使用会怎么样?
代码路径:Linux-C/CMakeLists/code/example_3
改写一下前面的CMakeLists.txt文件试试:

project(HELLO)
set(LIB_SRC hello.c)
set(APP_SRC main.c)
add_library(libhello ${LIB_SRC})
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

编译命令:
example_3/build$ cmake … -G “Unix Makefiles”
example_3/build$ make
目录树:

.
├── build
│   ├── hello
│   └── liblibhello.a
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

和前面相比,我们添加了一个新的目标 libhello,并将其链接进hello程序
里面有一点不爽,对不?
因为我的可执行程序(add_executable)占据了hello这个名字,所以add_library就不能使用这个名字了
然后,我们取了个libhello 的名字,这将导致生成的库为 libhello.lib(或 liblibhello.a),很不爽。
想生成 hello.lib(或libhello.a) 怎么办?
添加一行

set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

修改后的CMakeLists.txt如下:

project(HELLO)
set(LIB_SRC hello.c)
set(APP_SRC main.c)
add_library(libhello ${LIB_SRC})
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)
.
├── build
│   ├── hello
│   └── libhello.a
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

例四
在前面,我们成功地使用了库,可是源代码放在同一个路径下,还是不太正规,怎么办呢?分开放呗
我们期待是这样一种结构

.
├── build
├── CMakeLists.txt
├── libhello
│   ├── CMakeLists.txt
│   ├── hello.c
│   └── hello.h
└── src
    ├── CMakeLists.txt
    └── main.c

顶层目录的CMakeLists.txt文件

cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(src)
add_subdirectory(libhello)

src目录中的 CMakeLists.txt文件

message("PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(APP_SRC main.c)
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

libhello目录中的CMakeLists.txt文件

set(LIB_SRC hello.c)
add_library(libhello ${LIB_SRC})
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

编译命令:
example_4/build$ cmake … -G “Unix Makefiles”
example_4/build$ make
目录结构:

.
├── build
│   ├── libhello
│   │   └── libhello.a
│   └── src
│       └── hello
├── CMakeLists.txt
├── libhello
│   ├── CMakeLists.txt
│   ├── hello.c
│   └── hello.h
└── src
    ├── CMakeLists.txt
    └── main.c

回头看看,这次多了点什么,顶层的CMakeLists.txt文件中使用add_subdirectory告诉cmake去子目录寻找新的CMakeLists.txt子文件
在src的CMakeLists.txt文件中,新增加了include_directories,用来指明头文件所在的路径。
例五
前面还是有一点不爽:如果想让可执行文件在 bin 目录,库文件在lib目录怎么办?
就像下面显示的一样:
方法一:修改顶级的 CMakeList.txt 文件

cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(src bin)
add_subdirectory(libhello lib)
代码路径:Linux-C/CMakeLists/code/example_5_1
.
├── build
│   ├── bin
│   │   ├── hello
│   │   └── Makefile
│   ├── lib
│   │   ├── libhello.a
│   │   └── Makefile
│   ├── libhello
│   │   └── libhello.a
│   ├── Makefile
│   └── src
│       └── hello
├── CMakeLists.txt
├── libhello
│   ├── CMakeLists.txt
│   ├── hello.c
│   └── hello.h
└── src
    ├── CMakeLists.txt
    └── main.c

不是build中的目录默认和源代码中结构一样么,我们可以指定其对应的目录在build中的名字。
这样一来:build/src 就成了 build/bin 了,可是除了hello,中间产物也进来了。还不是我们最想要的。
方法二:不修改顶级的文件,修改其他两个文件

src/CMakeLists.txt 文件
message("PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(APP_SRC main.c)
set(EXECUTABLE_OUT_PATH ${PROJECT_BINARY_DIR}/bin) //新加
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)
libhello/CMakeList.txt 文件
set(LIB_SRC hello.c)
add_library(libhello ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) //新加
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

代码路径:Linux-C/CMakeLists/code/example_5_2
编译命令:
example_5_2/build$ cmake … -G “Unix Makefiles”
example_5_2/build$ make
生成目录:

myroot@myroot:~/Linux-C/CMakeLists/code/example_5_2/build$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  lib  libhello  Makefile  src
.
├── build
│   ├── bin
│   │   └── hello
│   └── lib
│       └── libhello.a
├── CMakeLists.txt
├── libhello
│   ├── CMakeLists.txt
│   ├── hello.c
│   └── hello.h
└── src
    ├── CMakeLists.txt
    └── main.c

编译的中间产物就不会进到build/bin build/lib目录
例六
在例三至五中,我们始终用的静态库,那么用动态库应该更酷一点吧。 试着写一下
如果不考虑windows下,这个例子应该是很简单的,只需要在上个例子的 libhello/CMakeList.txt 文件中的add_library命令中加入一个SHARED参数:
add_library(libhello SHARED ${LIB_SRC})
可是,我们既然用cmake了,还是兼顾不同的平台吧,于是,事情有点复杂:
修改 hello.h 文件

#ifndef __HELLO_H__
#define __HELLO_H__

#if defined _WIN32
	#if LIBHELLO_API
		#define LIBHELLO_API __declspec(dllexport)
	#else
		#define LIBHELLO_API __declspec(dllimport)
	#endif
#else 
#define LIBHELLO_API
#endif
LIBHELLO_API void hello(const char * fmt);

#endif

修改 libhello/CMakeLists.txt文件

set(LIB_SRC hello.c)
add_library(libhello SHARED ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

编译命令:
example_6/build$ cmake … -G “Unix Makefiles”
example_6/build$ make
生成目录:

.
├── build
│   ├── bin
│   │   └── hello
│   └── lib
│       └── libhello.so  // 生成的是.so动态库
├── CMakeLists.txt
├── libhello
│   ├── CMakeLists.txt
│   ├── hello.c
│   └── hello.h
└── src
    ├── CMakeLists.txt
    └── main.c

例八(重点学习)
将源文件和头文件分开在source和include文件夹,并且用source_group函数对文件进行归类到不同文件夹:
期望目录:

.
├── app
│   ├── appsource
│   ├── CMakeLists.txt
│   └── source
│       └── main.c
├── build
│   ├── bin
│   │   └── hello
│   └── lib
│       └── libhello.a
├── CMakeLists.txt
└── libhello
    ├── CMakeLists.txt
    ├── include
    │   └── hello.h
    ├── libsource
    └── source
        └── hello.c

顶层目录: CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(HELLO)

macro(source_group_by_dir source_files)
	set(sgbd_cur_dir ${CMAKE_CURRENT_SOURCE_DIR})
	foreach(sgbd_file ${${source_files}})
		string(REGEX REPLACE ${sgbd_cur_dir}/\(.*\) \\1 sgbd_fpath ${sgbd_file})
		string(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
		string(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
		if(sgbd_nogroup)
			set(sgbd_group_name "/")
		endif(sgbd_nogroup)
		source_group(${sgbd_group_name} FILES ${sgbd_file})
	endforeach(sgbd_file)
endmacro(source_group_by_dir)

add_subdirectory(libhello)
add_subdirectory(app)


app/source/main.c
//main.c
#include "hello.h"
#include <stdio.h>
int main()
{
    hello("Hello World!\n");
    return 0;
}

app/appsource
set(app 
/home/myroot/LinuxC/LinuxC/CMakeLists/code/example_8_source_group/app/source/main.c
)

app/CMakeLists.txt
message("PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR})
set(CMAKE_INCLUDE_DIRECTORIES_AFTER ON)
include_directories(${PROJECT_SOURCE_DIR}/libhello/include)

include(${CMAKE_CURRENT_SOURCE_DIR}/appsource)

source_group_by_dir(app) #调用source_group

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello ${app})
target_link_libraries(hello libhello)

libhello/source/hello.c
#include <stdio.h>
void hello(const char * fmt)
{
	printf("[hello] %s\n", fmt);
}

libhello/include/hello.h
#ifndef __HELLO_H__
#define __HELLO_H__

#if defined _WIN32
	#if LIBHELLO_API
		#define LIBHELLO_API __declspec(dllexport)
	#else
		#define LIBHELLO_API __declspec(dllimport)
	#endif
#else 
#define LIBHELLO_API
#endif
LIBHELLO_API void hello(const char * fmt);

#endif

libhello/libsource
set(hello 
/home/myroot/LinuxC/LinuxC/CMakeLists/code/example_8_source_group/libhello/include/hello.h
/home/myroot/LinuxC/LinuxC/CMakeLists/code/example_8_source_group/libhello/source/hello.c
)

libhello/CMakeLists.txt
include(${CMAKE_CURRENT_SOURCE_DIR}/libsource)

source_group_by_dir(hello) #调用source_group

add_library(libhello ${hello})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")