在这篇文章中,我们很高兴地宣布推出新版 Conan CLion 插件,现在它与 Conan 的 2.X 版本兼容。新插件旨在为使用 CLion(版本高于 2022.3)和 Conan 进行 C++ 项目开发的开发者带来更多便利和控制。我们将通过一个使用 libcurlstb 从互联网下载图像、加载图像并将其以 ASCII 艺术形式打印到控制台的实际示例,引导您完成使用此插件的过程。

插件的工作原理

在解释如何安装、配置和使用插件之前,让我们简要讨论一下它如何将 Conan 与 CMake 集成以创造无缝体验。此插件利用 cmake-conan,这是一个用于 Conan C 和 C++ 包管理器的 CMake 依赖项提供程序。它使用 CMAKE_PROJECT_TOP_LEVEL_INCLUDES 定义注入 conan_provider.cmake。此依赖项提供程序将 CMake 配置转换为 Conan。例如,如果您在 CLion 中选择 *调试* 配置文件,Conan 将安装并使用 *调试* 的包。

请记住,*cmake-conan* 在每次 CMake 调用 find_package() 时都会激活 Conan 集成,这意味着在 CMake 配置步骤运行之前不会安装任何库。此时,Conan 将尝试安装所需的库,并在需要时构建它们。

另外,请注意,由于依赖项提供程序是 CMake 中一个相对较新的功能,因此您需要 CMake 版本 >= 3.24 和 Conan >= 2.0.5。

安装插件

要安装新的 Conan CLion 插件,请导航到 JetBrains 市场。打开 CLion,转到 *设置 > 插件*,然后选择 *市场* 选项卡。搜索 Conan 插件,然后点击安装按钮。重新启动 CLion 后,一个新的“Conan”工具选项卡将出现在 IDE 底部。

创建新的 CMake 项目

首先,像往常一样在 CLion 中创建一个新的 CMake 项目。

CLion new CMake project

然后选择项目位置和您要使用的语言标准,然后点击“创建”。

配置插件

转到 IDE 底部的“Conan”工具选项卡。您将看到插件工具栏中唯一启用的操作是带有 ⚙️(齿轮)符号的操作,请点击它。

Click wrench symbol

首先,您应该配置要使用的 Conan 客户端可执行文件。您可以指向系统任意位置的特定安装文件,也可以选择 *“使用系统中安装的 Conan”* 以使用系统级安装的文件。

Configure Conan path

您会在其中找到一些标记为默认的选项。让我们逐一了解它们。

  • 首先,您会看到一些复选框,用于标记 Conan 应在哪些配置中管理依赖项。在我们的例子中,由于我们只有调试配置,因此它是唯一选中的配置。此外,在下方,“为所有配置自动添加 Conan 支持”默认情况下已选中。这意味着您无需担心为新的构建配置添加 Conan 支持,因为插件默认情况下会自动添加 Conan 支持。

  • 您还可以看到一个复选框,允许 Conan 更改默认的 CLion 设置,并按顺序运行 CMake 而不是并行运行。这是必要的,因为直到 Conan 2.0.9 版本,Conan 缓存还没有并发功能。

通常,如果您使用 Conan 插件,则不希望取消选中它们。因此,请保留它们,然后创建我们的项目并向其中添加库。所以,点击确定按钮,插件应该可以使用了。

注意:此时,CLion 将自动运行 CMake 的配置步骤。由于插件设置了 *conan.cmake* 依赖项提供程序,因此 CMake 输出中将出现一个警告,指示我们尚未向我们的 *CMakeLists.txt* 添加 find_package()。在我们向 *CMakeLists.txt* 文件添加必要的 find_package() 调用后,此警告将消失。

完成初始配置后,您会注意到库列表现在已启用,并且 🔄(更新)和 👁️(检查)符号也已启用。我们将在后面详细解释它们。

使用插件

现在我们已经配置并准备好了插件,让我们通过一个示例来探索它的用法。为此,我们将使用 libcurl 从互联网下载图像,然后使用 stb 加载图像,以及一些基本代码将其以 ASCII 字符的形式打印到控制台。

我们不会深入探讨代码的具体细节,因为这超出了本教程的范围。但是,此示例的所有源代码都可以在 Conan 2.0 示例库 中找到。

添加代码

首先,打开 CLion 生成的 *main.cpp* 文件,并将示例代码替换为以下代码(您也可以从 中复制)。

#include <iostream>
#include <curl/curl.h>
#include <vector>
#include <string>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

// Size of ASCII art
static const int new_width = 100;

// Ascii gradient
static const std::string ASCII_CHARS = " .:-=+#%@@"; // Inverted colors

// Function to scale the luminance into an ASCII character
char map_luminance_to_ascii(float luminance) {
    size_t position = luminance * (ASCII_CHARS.size() - 1);
    return ASCII_CHARS[position];
}

// Function to download image
static size_t write_data(void* ptr, size_t size, size_t nmemb, void* stream) {
    ((std::string*)stream)->append((char*)ptr, size * nmemb);
    return size * nmemb;
}

std::string download_image(const std::string& url) {
    CURL* curl = curl_easy_init();
    std::string response_string;

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);
    curl_easy_perform(curl);
    curl_easy_cleanup(curl);

    return response_string;
}

// Function to convert image to ASCII
std::string image_to_ascii(const std::string& image_data) {
    int width, height, channels;
    unsigned char* data = stbi_load_from_memory(
            reinterpret_cast<const stbi_uc*>(image_data.data()),
            image_data.size(),
            &width,
            &height,
            &channels,
            0
    );

    int new_height = static_cast<int>(static_cast<double>(height) / width * new_width * 0.45);

    std::string ascii_image;
    for (int i = 0; i < new_height; ++i) {
        for (int j = 0; j < new_width; ++j) {
            int old_i = i * height / new_height;
            int old_j = j * width / new_width;

            float r = data[(old_i * width + old_j) * channels + 0] / 255.0f;
            float g = data[(old_i * width + old_j) * channels + 1] / 255.0f;
            float b = data[(old_i * width + old_j) * channels + 2] / 255.0f;
            float luminance = (0.2126f * r + 0.7152f * g + 0.0722f * b); // Subtract the luminance from 1 to invert

            ascii_image += map_luminance_to_ascii(luminance);
        }
        ascii_image += '\n';
    }

    stbi_image_free(data);
    return ascii_image;
}

int main(int argc, char** argv) {
    // Picture by Katarzyna Modrzejewska from pexels.com
    std::string url = "https://images.pexels.com/photos/1314550/pexels-photo-1314550.jpeg";

    if (argc > 1) {
        url = argv[1];
    }

    std::string image_data = download_image(url);
    std::cout << image_to_ascii(image_data);
}

本质上,此应用程序接受图像 URL 作为参数(如果未提供,则默认为特定 URL),并使用 *libcurl* 和 download_image() 函数下载它。随后,它使用 *stb* 读取 RGB 值,并使用 image_to_ascii() 函数将亮度值转换为 ASCII 字符。

目前,尝试构建此代码将导致错误,因为我们还没有所需的库。导航到库列表并搜索 *libcurl*。将显示一些有关如何将其添加到 CMake 的信息,以及一个“在项目中使用”按钮。选择您要使用的版本并点击该按钮。

Select library and use

对 *stb* 执行相同的操作。

现在,如果您点击前面提到的 👁️(检查)图标,您将看到我们添加到项目中的所有库。这包括 CMake 的基本目标信息以及添加到 CMake 以使用它们的必要代码片段。

Conan 将有关已用包的信息存储在项目文件夹中的 *conandata.yml* 文件中。此文件由在此过程中创建的 *conanfile.py* 读取。这些文件可以针对插件的高级用法进行自定义,但请阅读相应文件中的信息,了解如何正确执行此操作。

Inspect libraries

根据说明修改您的 *CMakeLists.txt*,结果应该类似于以下内容

cmake_minimum_required(VERSION 3.25)
project(ascii_image)

set(CMAKE_CXX_STANDARD 17)

find_package(CURL)
find_package(stb)

add_executable(ascii_image main.cpp)

target_link_libraries(ascii_image CURL::libcurl stb::stb)

重新加载 CMake 项目后,您应该在 CMake 输出选项卡中看到 Conan 正在安装库。配置过程完成后,您可以构建并运行项目。

ASCII cat

结论

与 2.X 版本兼容的新版 Conan CLion 插件旨在提供 Conan 包管理器和 CLion IDE 之间的无缝集成。该插件提供了一个更直观的界面,使您能够更轻松地在 CLion 中直接管理依赖项。我们希望此工具能够增强您的开发体验,并且我们热切期待着看到您将用它构建什么!