资源文件需在package()中用self.copy()显式复制,exports_sources仅控制上传不参与打包,须配合package_info()暴露路径供构建系统使用。

conanfile.py 里用 package() 复制资源文件
资源文件(比如配置模板、shader、字体、图标)不是编译产物,但需要随包一起分发,就得在 package() 方法里显式复制。这个函数运行在构建完成后的打包阶段,工作目录是 build 目录,源码路径需通过 self.source_folder 或你自定义的路径变量访问。
常见错误是直接写相对路径(如 "./assets"),结果找不到——因为 package() 不在源码根目录执行。
- 用
os.path.join(self.source_folder, "assets")定位源资源目录 - 用
self.copy(pattern="*.json", dst="res/config", src=src_assets)指定目标子目录 - 若资源路径固定且不想每次 clone/download,可改用
exports_sources = "assets/**"提前纳入配方管理
exports_sources 和 package() 的分工容易混淆
exports_sources 只控制“哪些文件随 conanfile.py 一起上传到远程”,不参与打包逻辑;package() 才决定“最终包里实际包含哪些文件”。两者不是替代关系,而是协作关系。
典型误用:只设 exports_sources = "res/**",却没在 package() 里调用 self.copy() —— 结果包里空空如也。
-
exports_sources适合小而静态的资源(如 license、readme、少量模板) - 大资源或需按平台/配置筛选的(如不同分辨率的图片),必须在
package()里动态 copy - 如果资源来自 git submodule 或外部下载,只能走
package(),不能依赖exports_sources
资源路径在消费者项目中怎么引用
Conan 不自动把资源注入构建系统路径,你得靠 package_info() 暴露位置,再由 CMake 或其他构建工具读取。
例如,在 package_info() 里加:
def package_info(self):
self.cpp_info.set_property("resdirs", ["res"])
self.cpp_info.resdirs = ["res"]
这样 CMakeDeps 生成的 xxx-config.cmake 里就会有 set(xxx_RES_DIRS ".../res") 变量。消费者项目就能用 $<install_interface:>>></install_interface:> 获取路径。
- 别硬编码
"${CMAKE_CURRENT_SOURCE_DIR}/res"—— 这绕过了 Conan 的路径抽象,跨机器/CI 就失效 - 如果资源需在运行时加载,建议用
tools.files.collect_libs()类似思路封装一个collect_resources()工具函数 - Windows 下注意路径分隔符:用
os.path.join(),别拼"res\config\a.json"
多配置包里资源要不要区分 build_type/os
绝大多数资源文件是平台无关的(如 JSON 配置、文本模板),不需要为每个 build_type 单独打包一份。这时候应在 package_id() 中声明:
def package_id(self):
self.info.clear() # 或更精准地:self.info.build_type = "Any"
否则默认会为 Debug/Release 生成两个包,造成冗余。
- 只有当资源本身依赖编译配置时才需区分,比如:不同
build_type对应不同日志级别模板 - 若资源按
os分发(如 Windows 用 .dll.manifest,Linux 用 .so.conf),则保留self.info.os,但过滤掉build_type - 用
conan list <ref> --graph=file.json</ref>检查实际生成了几个二进制包,避免意外膨胀
资源文件放进 Conan 包的关键不在“放”,而在“放完之后怎么被正确找到和使用”——路径暴露、构建集成、包粒度控制这三点漏掉任何一环,都会导致本地能跑、CI 报错、客户集成失败。











