要点
- Conan本身是一个包管理工具, 类似Pyhon的pip, 本身用Python编写
- QT 6.0 开始引入Conan插件, 从个人观察来看, 似乎该插件仅仅引入了cmake脚本文件
- Conan本身不依赖QtCreator, 可以随意使用, 其他编译环境可以忽略掉本文章所有关于QT的内容, 其他不变
一 准备环境, 安装Conan
1.1 安装Python 3 及 pip3
1 brew install curl
2 brew install python3
3 curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
4 sudo python get-pip.py
5
6 python3 -m pip install --upgrade pip
7 python3 -m pip conan
8
9 #对于QtCreator, 此步骤必须, 否则会找不到conan指令
10 ln -s /opt/homebrew/bin/conan /usr/local/bin
11
12 conan --version
13 > Conan version 1.41.0
以上为OSX 环境安装步骤, 依赖brew, brew环境搭建见 链接
Windows 建议使用 官方MSI安装包
安装, 并勾选 pip组件
linux建议使用apt-get
yum
pacman
之类的包管理工具安装
二 设置Qt Creator
- Mac : 菜单
Qt Creator
--> 关于插件
, 在filter中输入conan, 勾选 Conan
插件
- Win : 菜单
帮助
--> 关于插件
, 在filter中输入conan, 勾选 Conan
插件
三 测试样例
- 创建Qt工程, 此处选择
Application (Qt Quick)
--> Qt Quick Application -Empty
, 构建方式选CMake
- 在工程
根目录
下创建 conanfile.txt, 录入:
-
1 [requires]
2 picojson/1.3.0
3
4 [generators]
5 cmake
- 在工程根目录下执行
mkdir build && cd build && conan install .. && cd -
- 在CMakeLists.txt 输入:
1 # 手工修改 第一处
2 ## 引入conan install产生的 cmake脚本
3 include(${CMAKE_CURRENT_SOURCE_DIR}/build/conanbuildinfo.cmake)
4 ## 引入脚本中创建的编译环境变量
5 include_directories(${CONAN_INCLUDE_DIRS_PICOJSON})
6 link_directories(${CONAN_LIB_DIRS_PICOJSON})
7
8 find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick LinguistTools REQUIRED)
9 find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick LinguistTools REQUIRED)
10
11 # 手工修改第二处
12 if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
13 qt_add_executable(RantionMidiCenter
14 MANUAL_FINALIZATION
15 ${PROJECT_SOURCES}
16 )
17 ## link conan引入的picojson静态库, 需要时使用
18 target_link_libraries(main ${CONAN_LIBS_PICOJSON})
19
20 qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
21 else()
22 if(ANDROID)
23 add_library(RantionMidiCenter SHARED
24 ${PROJECT_SOURCES}
25 )
26 else()
27 add_executable(RantionMidiCenter
28 ${PROJECT_SOURCES}
29 )
30 endif()
31
32 ## link conan引入的picojson静态库, 需要时使用
33 target_link_libraries(main ${CONAN_LIBS_PICOJSON})
34
35 qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
36 endif()
37
38
39 ## CONAN_LIBS_PICOJSON 中的 `CONAN_LIBS_xxxx` 可以替换成引入的其他库名称
- 在
main.cpp
头部输入:
1 #ifndef PICOJSON_USE_INT64
2 #define PICOJSON_USE_INT64
3 #include <picojson.h>
4 #endif
至此就可以随意使用picojson.h中的功能了
上述范例引入了一个 header only
的工具库 picojson.h, 仅用于演示功能, 实际上CONAN_LIB_DIRS_PICOJSON
与 CONAN_LIBS_PICOJSON
变量在CmakeLists
文件中的引入动作并无必要
四 2. 自建软件包
因为很多第三方开源项目没有 Conan 官方的支持,使用这些项目的时候就需要自己来编写 Receipe 来告诉 Conan 如何编译、打包第三方库了。
Conan打包过程
这里以bgfx为例来构造一个 Conan 的二进制包,并提交到我们自建的私仓中。
因为bgfx官方构建方式比较复杂,笔者选取了其 CMake 版本来执行构建。此外 Github 上已经有用户创建过对应的构建脚本了,因此这里可以直接参考别人的写法来进行构建。
使用 conan new Hello/0.1 -t
可以创建一个 HelloWorld 的空白 Receipe。可以看到呈现出这样的目录结构:
conanfile.py
test_package
CMakeLists.txt
conanfile.py
example.cpp
其中conanfile.py
用来描述构建软件包的编译打包过程,test_package
存储一个测试用的样例,用来检查是否成功构建了软件包并且是否能正常使用。
以构建bgfx
的脚本为例,修改conanfile.py
:
1 from conans import ConanFile, CMake
2 from distutils.dir_util import copy_tree
3
4 class BgfxConan(ConanFile):
5 name = "bgfx"
6 version = "20190604.018bbc4" # 这个地方我乱填的,请遵照SemVer的规范制定版本号
7 description = "Conan package for bgfx."
8 url = "https://github.com/9chu/bgfx-conan"
9 license = "BSD"
10 settings = "arch", "build_type", "compiler", "os" # 这些选项会被作为包的标识,区分不同的ABI
11 generators = "cmake"
12 options = {"shared": [True, False]}
13 default_options = "shared=False"
14
15 def source(self):
16 self.run("git clone https://github.com/JoshuaBrookover/bgfx.cmake")
17 copy_tree("bgfx.cmake", ".")
18 self.run("git reset --hard 018bbc4")
19 self.run("git submodule update --init --recursive")
20
21 def build(self):
22 cmake = CMake(self)
23 shared_options = "-DBUILD_SHARED_LIBS=ON" if self.options.shared else "-DBUILD_SHARED_LIBS=OFF"
24 fixed_options = "-DBGFX_BUILD_EXAMPLES=OFF"
25 tool_options = "-DBGFX_BUILD_TOOLS=OFF" if self.settings.os == "Emscripten" else ""
26 opengl_version = "-DBGFX_OPENGL_VERSION=33"
27 self.run("cmake %s %s %s %s %s" % (cmake.command_line, shared_options, fixed_options, tool_options, opengl_version))
28 self.run("cmake --build . %s -j8" % cmake.build_config)
29
30 def collect_headers(self, include_folder):
31 self.copy("*.h" , dst="include", src=include_folder)
32 self.copy("*.hpp", dst="include", src=include_folder)
33 self.copy("*.inl", dst="include", src=include_folder)
34
35 def package(self):
36 self.collect_headers("bgfx/include")
37 self.collect_headers("bimg/include")
38 self.collect_headers("bx/include" )
39 self.copy("*.a" , dst="lib", keep_path=False)
40 self.copy("*.so" , dst="lib", keep_path=False)
41 self.copy("*.lib", dst="lib", keep_path=False)
42 self.copy("*.dll", dst="bin", keep_path=False)
43
44 def package_info(self):
45 self.cpp_info.libs = ["bgfxd", "bimgd", "bxd"] if self.settings.build_type == "Debug" else ["bgfx", "bimg", "bx"]
46 self.cpp_info.libs.extend(["astc-codec", "astc", "edtaa3", "etc1", "etc2", "iqa", "squish", "nvtt", "pvrtc"])
47 if self.settings.os == "Macos":
48 self.cpp_info.exelinkflags = ["-framework Cocoa", "-framework QuartzCore", "-framework OpenGL", "-weak_framework Metal"]
49 if self.settings.os == "Linux":
50 self.cpp_info.libs.extend(["GL", "X11", "pthread", "dl"])
可以看到整个脚本分为若干个函数。其中:
source 函数
,用于源代码拉取和准备,比如对源码进行一些修改;
build 函数
,调用 CMake 进行构建;
package 函数
,用于执行打包操作;
package_info
则用于输出构建相关的信息,比如需要链接包中的哪些库文件。
默认的,Conan 以lib
、include
、bin
等文件夹标识头文件库文件目录,也可以在package_info函数
中进行修改。此外,Conan 提供了一系列的驱动包装函数来执行各种第三方工具,以及定制版本号规则、设定依赖等等。具体可以通过查阅官方文档来进一步了解,本文仅作抛砖引玉的目的。
在构建完成后,Conan 会以 Settings
和 Options
的取值 Hash 后为软件包指定一个 package_id
,因而不同的构建选项会对应到不同的 Id 上。最终拉取预编译包时就会以这个 Id 作为基准。因此,如果构建一个 Header-Only 的包,则可以去掉这些选项,这样在各个平台上都不需要额外构建即可使用了。
编写完 Receipe 后,就可以执行 Conan 来进行构建了。
conan install bgfx/20190604.018bbc4@9chu/stable --build=bgfx
构建完成后会自动执行test_package
的内容进行测试。最后使用命令上传到我们的私仓:
conan upload bgfx/20190604.018bbc4@9chu/stable --all -r=my_local_server
然后,我们就能在 Artifactory 中看到我们提交的包了。
配合 CMake 实战
上传完自己制作的包,接下来就可以将其作为依赖引入自己的项目之中了。
在这里,推荐使用 Conan 官方提供的 CMake 脚本将其引入项目中。
1 cmake_minimum_required(VERSION 3.1)
2 project(test CXX)
3
4 ######################################## conan package manager ########################################
5
6 # Download automatically, you can also just copy the conan.cmake file
7 if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
8 message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
9 file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.14/conan.cmake"
10 "${CMAKE_BINARY_DIR}/conan.cmake")
11 endif()
12
13 include(${CMAKE_BINARY_DIR}/conan.cmake)
14
15 ######################################## dependencies ########################################
16
17 conan_cmake_run(
18 REQUIRES
19 bgfx/20190604.018bbc4@9chu/stable
20 BASIC_SETUP CMAKE_TARGETS
21 BUILD missing)
22
23 ######################################## compiler flags ########################################
24
25 set(CMAKE_CXX_STANDARD 11)
26
27 if(MSVC)
28 add_definitions(-D_WIN32_WINNT=0x0600 -D_GNU_SOURCE -D_CRT_SECURE_NO_WARNINGS)
29 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
30 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8")
31 else()
32 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall -Wextra -Wno-implicit-fallthrough")
33 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -Wextra -Wno-implicit-fallthrough")
34 endif()
35
36 ######################################## targets ########################################
37
38 file(GLOB_RECURSE TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
39
40 add_executable(test ${TEST_SRC})
41 target_include_directories(test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
42 target_link_libraries(test CONAN_PKG::bgfx)
通过 conan_cmake_run
来直接指定依赖,这样就不需要写 conanfile.txt
并且会在 CMake 构建的时候自动把依赖安装上,集成到 CMake 中,非常的方便。
此外,由于使用了CMAKE_TARGETS
模式,依赖的指定方法也变成了CONAN_PKG::bgfx
的方式,这里可以根据需要自行调整。
附录1 Conan常用指令
# 在远程仓库搜索 picojson库, 包名支持通配符, 官方仓库为conancenter, conan-center已废弃
conan search picojson -r all
conan search picojson -r conancenter
conan search boost* -r conancenter
# 在本地缓存搜索 picojson库
conan search picojson
# 查看picojson/1.3.0包的详细信息, picojson为包名, 1.3.0为版本号
conan inspect picojson/1.3.0
# 根据 ..目录的conanfile.txt文件, 下载依赖包
conan install ..
# 远端管理
## 查阅所有远程仓库配置
conan remote list
## 添加源
conan remote add my-repo http://my-repo.com/xxx
# 或者使用insert来将其作为首个源
conan remote update my-repo http://my-repo.com/xxx --insert=0
# 删除一个源
conan remote remove my-repo
附录2 自建仓库
Conan 官方提供了一个迷你的服务端来提供仓库的功能,同时也推荐使用Artifactory Community Edition for C/C++来自建仓库。当然,如果是为了开源项目,可以直接在 Bintray 上申请一个开源项目账号来上传自制软件包而无需自建仓库(但是实在是太慢了)。
本文使用 Artifactory 来自建仓库,因为功能相对完善。Artifactory 支持配置用户和组以及对应的权限,商业版本还支持高可用和分布式。
安装
sudo docker pull docker.bintray.io/jfrog/artifactory-cpp-ce
sudo docker run -d --restart=always \
--name artifactory \
-v /data/artifactory:/var/opt/jfrog/artifactory \
-p 8081:8081 \
-e EXTRA_JAVA_OPTIONS='-Xms512m -Xmx2g -Xss256k -XX:+UseG1GC' \
docker.bintray.io/jfrog/artifactory-pro:latest
暴露在本机的8081端口上,同时挂载数据目录到/data/artifactory目录下
注意: Artifactory 默认会以 UserID 1030
运行服务,一定要注意宿主机的数据目录的权限是否满足要求,如果启动时出现 Permission Denied 错误,请修改目录权限:
sudo chown 1030:1030 /data/artifactory
sudo chmod 0755 /data/artifactory
你也可以使用--user 1234:4321
的方式来指定 Docker 使用哪个用户
/组
权限来执行容器。
配置
首次访问 http://localhost:8081 即可打开配置向导。默认的,配置好 Admin 账户的密码,初始化默认的 Conan 仓库配置即可。向导会提示你创建一个 HTTP Proxy,如果网络够好或者只作私仓使用则可以忽略该配置,否则,建议创建一个代理设置来加速访问 Remote 仓库。
Artifactory 提供了三种类型的 Conan 仓库供不同目的使用:
- 本地仓库(Local):即当前 Artifactory 服务器上存储软件包的仓库;
- 远端仓库(Remote):即第三方的软件仓库,在本服务器上作为 Proxy 和 Cache 运作;
- 虚拟仓库(Virtual):作为一个索引中心,能将其他两类仓库整合到一个仓库名下,方便使用。
初始化之后,Artifactory 会创建好一个本地仓库,一个到 Conan Center 的远程仓库和包含上述两个仓库的虚拟仓库。
需要注意的是,默认配置下 Artifactory 是允许匿名访问的,必须在 Admin > Security Configuration 页面中关闭匿名访问才能真正作为私仓使用。
添加到 Conan
在仓库管理页面,点击虚拟仓库 Conan
,在右上角点击 Set Me Up
按钮即可显示当前仓库如何在 Conan 中配置并作为一个源。