iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0

從 Conan 第一篇開始,我們一路都用 conanfile.txt 作為 Conan 管理套件的入口。

如果你只是想在既有的專案中快速引入幾個函式庫,conanfile.txt 最方便,簡單。

但我們今天碰到了一個小問題。

假設有個情境:我們要想寫一個具備 GPU 平行運算能力的跨平台小程式,Windows / macOS / Linux 三個平台都要支援。

Windows 和 Linux 上我們打算用 OpenCL 來跑 GPU 運算。我們也很快的搜尋一下,發現 ConanCenter 上面已經有 opencl-icd-loader/2025.07.22 現成套件可以用。

但是很不巧, macOS 不能用 OpenCL。為什麼呢?原來蘋果早在 2018 年 WWDC 就已經正式宣布棄用 macOS/iOS 上的 OpenCL,舊版本凍結不再更新,並引導開發者全面改用蘋果自己的 Metal 框架。

所以在編譯 macOS 版本的時候,我們得改用 Metal 而非 OpenCL。

挖,這樣 conanfile.txt 要怎麼寫?

conanfile.txt 沒有語法可寫「只有 Windows/Linux 才要」這樣的邏輯。當然你也可以每個平台各放一份 conanfile.txt,但那樣很快就會變成維護地獄。

怎麼辦呢?

其實解法很簡單,就是換成 conanfile.py

換成 conanfile.py

雖然 conanfile.txt 是常見的入口,但 Conan 還有另一個更強大的入口檔案叫 conanfile.py

conanfile.py 是個純正的 python 腳本,任何的程式邏輯都能寫,所以 conanfile.txt 做不到的事情,對 conanfile.py 可說是一點難度都沒有。

以下就是我們這次要使用的範例:

from conan import ConanFile
from conan.tools.cmake import cmake_layout

class HelloComputeRecipe(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires("fmt/12.2.0")
        if self.settings.os in ("Windows", "Linux"):
            self.requires("opencl-icd-loader/2025.07.22")

    def layout(self):
        cmake_layout(self)

暫時不理會其他細節,先看 requirements() 這個函數,這裡用一個簡單的 if 就把平台邏輯輕鬆解決了:只有 Windows & Linux 需要安裝 OpenCL 套件, macOS 就跳過。 因為 macOS 用的 Metal framework 是系統內建的,不需要透過 Conan 取得。

這樣問題就解決了,用我們程式設計師每天都在用的 if 條件式,而且是再熟悉不過的 python 語法。

比對我們之前用的 conanfile.txt,conanfile.py 的內容組成其實很容易對應起來。

[requires] 區塊變成 requirements() 方法。

原本每一行寫一個函式庫,現在變成每一行寫一個 self.requires()。

[generators] 區塊對應到 generators 屬性。

[layout] 區塊對應到 layout() 方法裡呼叫的 cmake_layout(self)。

以上都是我們前幾天在 conanfile.txt 已經多次使用的部分。

唯一可能沒見過的是這一行: settings = "os", "compiler", "build_type", "arch"

這是宣告這份入口檔案會用到哪些 settings。有宣告才能在 requirements() 裡讀 self.settings.os。這些值來自 profile。

步驟一:創建專案

接下來我們就親手來建立這個專案囉,這是專案的目錄結構:

hello-compute/
├── conanfile.py
├── CMakeLists.txt
└── src/
    ├── compute.h
    ├── main.cpp
    ├── compute_opencl.cpp
    └── compute_macos.mm

Windows 跟 Linux 這次共用同一支 compute_opencl.cpp,因為兩邊都是 OpenCL 運算,程式碼一樣。

只有 macOS 因為使用完全不同的 Metal API 運算,需要獨立一個檔案。

步驟二:撰寫 CMakeLists.txt

接下來寫 CMake。

cmake_minimum_required(VERSION 3.25)
project(hello_compute CXX)

find_package(fmt REQUIRED)

add_executable(hello_compute src/main.cpp)
target_compile_features(hello_compute PRIVATE cxx_std_17)
target_link_libraries(hello_compute PRIVATE fmt::fmt)

if(WIN32 OR LINUX)
    find_package(OpenCL REQUIRED)
    target_sources(hello_compute PRIVATE src/compute_opencl.cpp)
    target_link_libraries(hello_compute PRIVATE OpenCL::OpenCL)
elseif(APPLE)
    enable_language(OBJCXX)
    target_sources(hello_compute PRIVATE src/compute_macos.mm)
    target_link_libraries(hello_compute PRIVATE "-framework Metal" "-framework Foundation")
endif()

步驟三:撰寫程式碼

這個部份我就不多作解釋了。作為範例專案,這裡簡單實做了一個列出GPU的功能。

總共有四個原始檔

compute.h:

#pragma once

void print_compute_info();

main.cpp:

#include <fmt/base.h>
#include "compute.h"

int main() {
    fmt::print("Hello, compute!\n");
    print_compute_info();
    return 0;
}

compute_opencl.cpp`(Windows / Linux 共用):

#include "compute.h"
#include <fmt/base.h>

#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>

#include <vector>

void print_compute_info() {
    cl_uint platform_count = 0;
    clGetPlatformIDs(0, nullptr, &platform_count);

    std::vector<cl_platform_id> platforms(platform_count);
    clGetPlatformIDs(platform_count, platforms.data(), nullptr);

    for (cl_platform_id platform : platforms) {
        char platform_name[256] = {};
        clGetPlatformInfo(platform, CL_PLATFORM_NAME, sizeof(platform_name), platform_name, nullptr);
        fmt::print("Platform: {}\n", platform_name);

        cl_uint device_count = 0;
        clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 0, nullptr, &device_count);

        std::vector<cl_device_id> devices(device_count);
        clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, device_count, devices.data(), nullptr);

        for (cl_device_id device : devices) {
            char device_name[256] = {};
            clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(device_name), device_name, nullptr);
            fmt::print("  [{}]\n", device_name);
        }
    }
}

compute_macos.mm(Metal):

#include "compute.h"
#include <fmt/base.h>

#import <Metal/Metal.h>

void print_compute_info() {
    id<MTLDevice> device = MTLCreateSystemDefaultDevice();
    if (device == nil) {
        fmt::print("No Metal device found\n");
        return;
    }

    fmt::print("Platform: Metal\n");
    fmt::print("  [{}]\n", [[device name] UTF8String]);
}

步驟四:Conan Install 安裝套件

命令列進入專案目錄,然後打以下指令:

conan install . --build=missing

Conan 會自己找到 conanfile.py,並且安裝套件。

去看命令列輸出,Windows / Linux 上面顯示有安裝 OpenCL:

======== Computing dependency graph ========
Graph root
    conanfile.py: C:\github\ironman-2026\D07\conanfile.py
Requirements
    fmt/12.2.0#b445244e6d0151a425e229248cd2bed8 - Cache
    opencl-clhpp-headers/2025.07.22#4eb7c91d676965f46d435c07cd7244bb - Cache
    opencl-headers/2025.07.22#37f30f890e7e61f7baa895c1eb06a692 - Cache
    opencl-icd-loader/2025.07.22#4485cccf0de603eb83ecd97a290d482e - Cache

相對的,macOS 上只顯示了 fmt,OpenCL 系列套件完全不會出現:

======== Computing dependency graph ========
Graph root
    conanfile.py: /Users/matt/github/ironman-2026/D07/conanfile.py
Requirements
    fmt/12.2.0#b445244e6d0151a425e229248cd2bed8 - Cache

所以 conanfile.py 的條件式發揮作用了

步驟五:編譯與執行

接下來,我們就依照標準的 CMake 步驟去編譯專案:

Windows

cmake --preset conan-default
cmake --build --preset conan-release
build\Release\hello_compute.exe

macOS / Linux

cmake --preset conan-release
cmake --build --preset conan-release
./build/Release/hello_compute

編譯完跑起來,在我的 Windows 上看到 GPU 裝置了!

Hello, compute!
Platform: Intel(R) OpenCL Graphics
  [Intel(R) Arc(TM) 140T GPU (32GB)]
Platform: NVIDIA CUDA
  [NVIDIA RTX PRO 500 Blackwell Generation Laptop GPU]

到此大功告成。

在這一章中,我們在 conanfile.py 裡面寫條件式,決定要不要引入 OpenCL 依賴,以後我們會更加倚賴 conanfile.py,在後續介紹更多進階的功能。

明天開始,我們先轉換到 vcpkg 系列。


上一篇
D06:Makefile 搭配 Conan 套件管理器
下一篇
D08:回來談談 Conan Profile
系列文
C++ 套件管理器求生指南 Conan vs Vcpkg 共 11 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言