C++编程 之 CMake实践
Posted 算法妖怪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++编程 之 CMake实践相关的知识,希望对你有一定的参考价值。
今天讲代码构建,为什么呢?因为包括妖哥我本人发现,研究深度学习机器学习这帮人,以至于部分在校学生都习惯使用python或matlab,很少直接使用C++ /C做完整项目,从而相关的一些操作和技能掌握并不全面。面试算法攻城狮岗位,算法不算法我不知道,前提必须首先是攻城狮,算法是其上锦上添花的东西。所以2021年,我会更多关注于落地项目要求的一些技能,同时在涉及机器学习深度学习方面,除了盘点完基础知识外,会以研究方向,热门论文,数学理论等不同角度写点东西。多看多写多交流。
好了,说CMake,使用起来很简单,在不使用IDE的情况下,我们才使用CMake(www.cmake.org).
本篇文章是在《CMake Practice》一文基础上实践和删减而成。文末附带下载连接。
CMake特点:
1.开源;
2.跨平台(makefile, xcode, MSVC);
3.简化编译构建工程和编译过程,方便管理大型项目;
4.高效,可扩展;
5.简单,也不是很简单,编写过程实际上也是编程过程。
使用建议:
1.如果没有实际需求可以不学;
2.如果简单只有几个文件,可以直接写Makefile;
3.如果是C/C++/java之外的语言可以不学;
4.如果有IDE等非常完备的构建体系,可以不学。
初试CMake
1.准备工作:
创建一个目录t1,在该目录下创建文件main.cpp和CMakeLists.txt(注意大小写):
main.cpp中写入:
int main(){
std::cout<<"hello world from CMakelist."<<std::endl;
return 0;
}
CMakeLists.txt内容:
PROJECT(HELLO)
SET(SRC_LIST main.cpp)
MESSAGE(STATUS "This is BINARY dir "${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
2.开始构建
在t1目录下 cmake .
可见生成了:CMakeFiles, CMakeCache.txt, cmake_install.cmake, Makefile等文件.
再 make
生成hello 二进制执行文件。
运行 ./hello
得到输出 hello world from CMakelist.
如果需要看到make构建的详细过程,可以使用 make VERBOSE=1或者VERBOSE=1 make 进行构建。
3.简单解释
CMakeLists.txt 是CMake的构建定义文件,如果工程存在多个目录,可以每个目录都存在一个CMakeLists.txt。看其内容:
PROJECT(projectname [CXX] [C] [Java])
定义工程名称,并可指定工程支持的语言(可忽略)。这个指令隐式定义两个cmake变量:
<projectname>_BINARY_DIR 以及<projectname>_SOURCE_DIR
同时 cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR 变量,他们的值分别跟 <projectname>_BINARY_DIR 和<projectname>_SOURCE_DIR 一致。
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可。
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"
...)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR,产生错误,生成过程被跳过;
SATUS,输出前缀为—的信息;
FATAL_ERROR,立即终止所有 cmake 过程.。
ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中你也可以直接写成 ADD_EXECUTABLE(hello main.cpp)。
将本例改写成一个最简化的 CMakeLists.txt:
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.cpp)
4.基本语法规则
变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名;
指令(参数 1 参数 2...) 参数使用括弧括起,参数之间使用空格或分号分开;
指令是大小写无关的,参数和变量是大小写相关的。推荐全部使用大写指令;
这里需要特别解释的是作为工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的。hello 定义了可执行文件的文件名,你完全可以写成:ADD_EXECUTABLE(t1 main.cpp)
编译后会生成一个 t1 可执行文件。
5.语法的疑惑
cmake 的语法还是比较灵活而且考虑到各种情况,比如 SET(SRC_LIST main.cpp)也可以写成 SET(SRC_LIST “main.cpp”) 是没有区别的,但是假设一个源文件的文件名是 fu nc.cpp(文件名中间包含了空格)。这时候就必须使用双引号,如果写成了 SET(SRC_LIST fu nc.cpp),就会出现错误,提示你找不到 fu 文件和 nc.cpp 文件。这种情况,就必须写成: SET(SRC_LIST “fu nc.cpp”)
此外,你可以可以忽略掉 source 列表中的源文件后缀,比如可以写成 ADD_EXECUTABLE(t1 main),cmake 会自动的在本目录查找 main.c 或者 main.cpp 等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.cpp, 一个 main.
同时参数也可以使用分号来进行分割。下面的例子也是合法的:
ADD_EXECUTABLE(t1 main.c t1.cpp)可以写成 ADD_EXECUTABLE(t1 main.c;t1.cpp).
MESSAGE(STATUS “This is BINARY dir” ${HELLO_BINARY_DIR}) 也可以写成:MESSAGE(STATUS “This is BINARY dir ${HELLO_BINARY_DIR}”)
6.清理工程
make clean
7.内部构建和外部构建
上面的例子展示的是“内部构建”,相信看到生成的临时文件比您的代码文件还要多的时候,估计这辈子你都不希望再使用内部构建。
对于 cmake,内部编译上面已经演示过了,它生成了一些无法自动删除的中间文件,所以,引出了我们对外部编译的探讨,外部编译的过程如下:
首先,清除 t1 目录中除 main.cpp CmakeLists.txt 之外的所有中间文件,最关键的是 CMakeCache.txt。
在 t1 目录中建立 build 目录,当然你也可以在任何地方建立 build 目录,不一定必须在工程目录中。
进入 build 目录,运行 cmake ..(注意,..代表父目录,因为父目录存在我们需要的CMakeLists.txt,如果你在其他地方建立了 build 目录,需要运行 cmake <工程的全路径>),查看一下 build 目录,就会发现了生成了编译需要的 Makefile 以及其他的中间 文件.
运行 make 构建工程,就会在当前目录(build 目录)中获得目标文件 hello。
上述过程就是所谓的 out-of-source 外部编译,一个最大的好处是,对于原有的工程没 有任何影响,所有动作全部发生在编译目录。
这里需要特别注意的是:通过外部编译进行工程构建,HELLO_SOURCE_DIR 仍然指代工程路径,即
XXX/t1,而 HELLO_BINARY_DIR 则指代编译路径,即XXX /t1/build。
外部构建与安装
后面所有的构建我们都将采用 out-of-source 外部构建,约定的构建目录是工程目录下的 build 自录。
本小节的任务是让前面的 Hello World 更像一个工程,我们需要作的是:
为工程添加一个子目录 src,用来放置工程源代码;
添加一个子目录 doc,用来放置这个工程的文档 hello.txt
在工程目录添加文本文件 COPYRIGHT, README;
在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制
将构建后的目标文件放入构建目录的 bin 子目录;
最终安装这些文件:将 hello 二进制与 runhello.sh 安装至/usr/bin,将 doc 目录 的内容以及 COPYRIGHT/README 安装到/usr/share/doc/cmake/t2。
1.准备工作
创建 t2目录,将t1中的main.cpp和CMakeLists.txt拷贝到t2中;
添加子目录src,将main.cpp移动到src 目录中,在src 中创建一个CMakeLists.txt,编写内容如下:
ADD_EXECUTABLE(hello main.cpp);
将t2目录下的所谓工程CMakeLists.txt内容修改为:
PROJECT(HELLO)ADD_SUBDIRECTORY(src bin)
然后建立build目录,进入该目录,执行
cmake ..
make
生成的文件hello位于 build/bin目录中。
2.语法解释
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除。上面的例子定义了将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为 bin 目录。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在 build/src 目录(这个目录跟原有的 src 目录对应),指定 bin 目录后,相当于在编译时将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录。
3.另存为目标二进制
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
在第一节我们提到了<projectname>_BINARY_DIR 和 PROJECT_BINARY_DIR 变量,他们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT_SOURCE_DIR 也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build目录。应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的 CMakeLists.txt?,把握一个简单的原则,在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义。
4.如何安装
需要引入一个新的 cmake 指令 INSTALL 和一个非常有用的变量 CMAKE_INSTALL_PREFIX。
CMAKE_INSTALL_PREFIX 变量类似于 configure 脚本的 –prefix,常见的使用方法看起来是这个样子:
cmake -DCMAKE_INSTALL_PREFIX=/usr .
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
目标文件的安装:
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来 定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
举个简单的例子:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子会将:
可执行二进制 myrun 安装到${CMAKE_INSTALL_PREFIX}/bin 目录
动态库 libmylib 安装到${CMAKE_INSTALL_PREFIX}/lib 目录
静态库 libmystaticlib 安装到${CMAKE_INSTALL_PREFIX}/libstatic 目录
特别注意的是你不需要关心 TARGETS 具体生成的路径,只需要写上 TARGETS 名称就可以了。
普通文件的安装:
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限 PERMISSIONS,安装后的权限为:OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即 644 权限。
非目标文件的可执行程序安装(比如脚本之类):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为: OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限。
目录的安装:
INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
这里主要介绍其中的 DIRECTORY、PATTERN 以及 PERMISSIONS 参数。DIRECTORY 后面连接的是所在 Source 目录的相对路径,但务必注意:abc 和 abc/有很大的区别。如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。PATTERN 用于使用正则表达式进行过滤,PERMISSIONS 用于指定 PATTERN 过滤后的文件权限。
我们来看一个例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
这条指令的执行结果是:
将 icons 目录安装到 <prefix>/share/myproj,将 scripts/中的内容安装到 <prefix>/share/myproj
不包含目录名为 CVS 的目录,对于 scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
5 修改 Helloworld 支持安装
添加目录和文件:t2/doc t2/doc/hello.txt t2/runhello.sh t2/COPYRIGHT t2/README
安装 COPYRIGHT/README,直接修改主工程文件 CMakelists.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
安装 runhello.sh,直接修改主工程文件 CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
因为 hello.txt 要安装到/<prefix>/share/doc/cmake/t2,所以我们不能直接安装整个 doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用”doc/”在工程文件中添加:
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
6 make & install
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
make
make install
如果我没有定义 CMAKE_INSTALL_PREFIX 会安装到什么地方?你可以尝试以下,cmake ..;make;make install,你会发现CMAKE_INSTALL_PREFIX 的默认定义是/usr/local。
静态库与共享库构建
1.准备工作
创建t3目录,再创建lib目录,CMakeLists.txt,CMakeLists.txt内容:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib目录下创建hello.cpp和hello.h:
hello.cpp内容:
void HelloFunc(){
std::cout<<"hello world ! "<<std::endl;
}
hello.h内容:
void HelloFunc();
在lib目录下创建CMakeLists.txt,内容:
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
2.编译共享库(动态库)
仍然采用 out-of-source 编译的方式,按照习惯,我们建立一个 build 目录,在 build 目录中
cmake ..
make
这时,你就可以在 lib 目录得到一个 libhello.so。如果你要指定 libhello.so 生成的位置,可以通过在主工程文件 CMakeLists.txt 中修改 ADD_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者在 lib/CMakeLists.txt 中添加SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置。
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
你不需要写全 libhello.so,只需要填写 hello 即可,cmake 系统会自动为你生成 libhello.X。类型有三种:
SHARED,动态库
STATIC,静态库
MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
3.添加静态库
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然后再在 build 目录进行外部编译,我们会发现,静态库根本没有被构建,仍然只生成了一个动态库。因为 hello 作为一个 target 是不能重名的,所以,静态库构建指令无效。
修改为 ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC}) 就可以构建一个 libhello_static.a 的静态库了。但是名字改变了。
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。在本例中,我们需要作的是向 lib/CMakeLists.txt 中添加一条:SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") 这样,我们就可以同时得到 libhello.so/libhello.a 两个库了。与他对应的指令是:GET_TARGET_PROPERTY(VAR target property) 。
libhello.a 已经构建完成,位于 build/lib 目录中,但是 libhello.so 去消失了。这个问题的原因是:cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.a 时, 就会清理掉libhello.so.
为了回避这个问题,比如再次使用 SET_TARGET_PROPERTIES 定义
CLEAN_DIRECT_OUTPUT 属性。向 lib/CMakeLists.txt 中添加:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
重新构建,会发现 build/lib 目录中同时生成了 libhello.so 和 libhello.a 。
4.动态库版本号
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION 指代动态库版本,SOVERSION 指代 API 版本。将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果。在 build/lib 目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
5.安装共享库和头文件
我们向 lib/CMakeLists.txt 中添加如下指令:
INSTALL(TARGETS hello hello_staticLIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,静态库要使用 ARCHIVE 关键字通过:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
我们就可以将头文件和共享库安装到系统目录/usr/lib 和/usr/include/hello 中了。
如何调用外部共享库和头文件
上一节我们已经完成了 libhello 动态库的构建以及安装,本节我们的任务很简单:编写一个程序使用我们上一节构建的共享库。
1.准备工作
创建t4目录,创建src子目录,在该目录下编写 main.cpp,内容如下:
int main(){
HelloFunc();
return 0;
}
t4目录下编写工程CMakeLists.txt:
PROJECT(NEWHELLO)ADD_SUBDIRECTORY(src)
编写src/CMakeLists.txt:
ADD_EXECUTABLE(main main.cpp)
2.外部构建
构建失败,如果需要查看细节,make VERBOSE=1 来构建,错误为找不到hello.h。
为了让我们的工程能够找到 hello.h 头文件,我们需要引入一个新的指令 INCLUDE_DIRECTORIES,其完整语法为:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:
CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面。
通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前。
现在我们在 src/CMakeLists.txt 中添加一个头文件搜索路径,方式很简单,加入:
INCLUDE_DIRECTORIES(/usr/include/hello)
重新构建,出现新错误,链接不上HelloFunc,因为我们没有link到共享库libhello.so。
TARGET_LINK_LIBRARIES(target library1
<debug | optimized> library2
...)
这个指令可以用来为 target 添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。src/CMakeLists.txt 中添加如下指令:
TARGET_LINK_LIBRARIES(main hello)
也可以写成
TARGET_LINK_LIBRARIES(main libhello.so)
这里的 hello 指的是我们上一节构建的共享库 libhello.
让我们来检查一下 main 的链接情况:
ldd src/main
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
可以清楚的看到 main 确实链接了共享库 libhello,而且链接的是动态库 libhello.so.1。
那如何链接到静态库呢?
方法很简单:
将 TARGET_LINK_LIBRRARIES 指令修改为:
TARGET_LINK_LIBRARIES(main libhello.a)
ldd src/main
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
3.特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
务必注意,这两个是环境变量而不是 cmake 变量。使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者 CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
以上是4个demo说明一些使用场景,基本够用了。至于CMake其他常用命令和常用变量,常用环境变量,请详见 《Cmake Practice》,这里不再介绍。
这里妖哥给出一个自己在实际使用的例子(android):
cmake_minimum_required(VERSION 3.4.1)
include_directories(
${CMAKE_SOURCE_DIR}/libs/native/XXX/
)
#eigen
include_directories(${CMAKE_SOURCE_DIR}/libs/native/eigen3)
# mnn
include_directories(${CMAKE_SOURCE_DIR}/libs/native/MNN)
# opencv
include_directories(${CMAKE_SOURCE_DIR}/libs/native/opencv/jni/include)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fvisibility-inlines-hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math -fno-rtti -fno-exceptions -flax-vector-conversions")
find_package (Threads)
# opencv
set(LIBOPENCV_DIR ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI})
add_library(opencv_java3 SHARED IMPORTED)
set_target_properties(opencv_java3 PROPERTIES IMPORTED_LOCATION ${LIBOPENCV_DIR}/libopencv_java3.so)
add_library( mnn SHARED IMPORTED )
set_target_properties(
mnn
PROPERTIES IMPORTED_LOCATION
${LIBOPENCV_DIR}/libMNN.so
)
add_library( mnn_cl SHARED IMPORTED )
set_target_properties(
mnn_cl
PROPERTIES IMPORTED_LOCATION
${LIBOPENCV_DIR}/libMNN_CL.so
)
add_library( XXX SHARED
src/main/cpp/config.cpp
src/main/cpp/XXX.cpp
...
)
find_library( log-lib log )
target_link_libraries( XXX
mnn
mnn_cl
opencv_java3
m
z
gomp
${log-lib} )
《Cmake Practice》相关链接:
链接:https://pan.baidu.com/s/1Ufq0VQK0CkeWvmQbsk2Y2g 提取码:hz5t
以上是关于C++编程 之 CMake实践的主要内容,如果未能解决你的问题,请参考以下文章