原来习惯了在Windows下使用cmake+vcpkg编译C/C++程序,但是vcpkg有个缺点,每次更新都需要从github下载源码编译库文件。像qt那样的大型库,每次光是编译动不动就浪费一整天时间实在耗不起,更别说DNS导致的连接问题了。

先梳理下需求,对C/C++包管理器的要求主要以下3点:

  1. 支持gtk+、qt、wxWidgets、cgal、vtk等常用库。
  2. 支持跨平台和IDE集成,或者支持CMake。
  3. 及时更新。

能满足条件的除了vcpkg就只剩xrepo了。 xrepo是xmake自带的包管理器,xmake1是一个新兴的C/C++构建工具。这篇文章尝试使用xmake+repo替代cmake+vcpkg,看能否满足现阶段C/C++程序的编译需求。

1. 安装xmake

按照官方文档2的指导,这里用posershell命令直接下载安装:

irm https://xmake.io/psget.text | iex

在Windows下执行以上命令,自动安装在C:\Users\[用户名]\xmake路径下。

也可以使用其他方式安装,比如直接从Release 页面下载对应平台的安装包手动安装。

安装完xmake之后xrepo就可以使用了,查看xmake、xrepo的版本:

xmake --version
xrepo --version

cd8eda41c5999fbe3a3714490ad3369c.png

vscode可以安装以下拓展,用于查看和构建xmake项目。

2. 创建项目

可以直接使用xmake的create命令创建项目,默认为C++项目。

xmake create hello_cpp

f122d0c81fc444640af563890205868d.png

也可以增加-l开关指定项目的语言类型。

xmake create -l c hello_c

打开hello_c项目自动生成的xmake.lua文件,后面一大段注释告诉用户怎么配置、构建和安装项目。直接在项目文件夹中执行xmake命令即可编译。

使用vscode安装了拓展的话,也可以通过下方的按钮执行编译命令。

4d7371ad8f240c02d2c5e07b6c104cd2.png

在vscode右侧的拓展窗口可以配置编译用的工具链、平台和调试信息。

3fe3de466fa2444dab9e6f66128556a3.png

3. 指定工具链

xmake可以自动解析系统自带的msvc和msvc-clang工具链,可以通过拓展轻松切换,但msys2和mingw的安装路径无法自动解析。可以通过在xmake.lua文件中添加自定义工具链3解决。

toolchain("msys2-mingw64") -- 自定义工具链,名称 msys2-mingw64
    set_kind("standalone") -- 独立工具链
    set_sdkdir("D:/opt/msys64/mingw64") -- msys2-mingw64 工具链 sdk 目录
toolchain_end() 

target("hello_c")
    set_kind("binary")
    add_files("src/*.c")
    set_toolchains("msys2-mingw64") -- 将当前target绑定到上面的自定义工具链
target_end()

或者通过以下命令行配置并使用msys2-mingw64的工具链编译。

xmake f --toolchain=mingw --mingw='D:/opt/msys64/mingw64'
xmake b

如果是msys2-clang64工具链,通过以下命令指定。

xmake f -c --sdk='D:/opt/msys64/clang64' --toolchain=llvm 
xmake b -v

4. 启用调试

配置编译调试版本。

xmake f -m debug
xmake b

启用调试并运行。

xmake run -d hello_c # hello_c是项目输出的target名称

也可以指定调试器,需要在编译前配置好。

xmake f --debugger=lldb -m debug
xmake b
xmake run -d hello_c

在Windows下只能使用msvc自带的调试器,尝试使用msys2工具链的gdb、lldb都未能成功,不清楚xmake是怎么解析调试器的安装路径的……

5. 添加依赖(编译cgal项目)

接下来创建一个cgal项目。

xmake create cgal_test

可以先通过xrepo预先安装cgal库,从官方库中下载依赖。

xrepo install cgal
  • 如果不执行这一步,到了执行xmake编译命令的时候会提醒你是否下载cgal依赖,不过有很大概率会安装失败。
  • 而且就算执行这一步,到了执行xmake编译命令时还是会提醒你是否下载cgal依赖,只是没那么容易失败,不明白包管理器xrepo是怎么规划的……
  • 下载很慢,国内嘛你懂的,有条件还是上梯子吧。

编辑xmake.lua文件增加依赖库支持。

-- xmake.lua
add_rules("mode.debug", "mode.release")
add_requires("cgal") -- 添加xrepo依赖请求
set_languages("c++17") -- 开启C++17支持

target("cgal_test")
set_kind("binary")
add_packages("cgal") -- 添加cgal包引用
add_files("src/main.cpp")
target_end()
  • 通过add_requires()在文件中添加cgal的依赖。
  • 通过add_packages()在target中引入cgal包。
  • cgal编译需要用到C++17,通过set_languages("c++17")开启C++17支持。

编辑main.cpp,参考cgal官方源码4

// main.cpp
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Mesh_triangulation_3.h>
#include <CGAL/Mesh_complex_3_in_triangulation_3.h>
#include <CGAL/Mesh_criteria_3.h>
#include <CGAL/Labeled_mesh_domain_3.h>
#include <CGAL/make_mesh_3.h>

// Domain
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::FT FT;
typedef K::Point_3 Point;
typedef FT(Function)(const Point &);
typedef CGAL::Labeled_mesh_domain_3<K> Mesh_domain;

#ifdef CGAL_CONCURRENT_MESH_3
typedef CGAL::Parallel_tag Concurrency_tag;
#else
typedef CGAL::Sequential_tag Concurrency_tag;
#endif

// Triangulation
typedef CGAL::Mesh_triangulation_3<Mesh_domain, CGAL::Default, Concurrency_tag>::type Tr;
typedef CGAL::Mesh_complex_3_in_triangulation_3<Tr> C3t3;
// Criteria
typedef CGAL::Mesh_criteria_3<Tr> Mesh_criteria;
namespace params = CGAL::parameters;
// Function
FT sphere_function(const Point &p)
{
    return CGAL::squared_distance(p, Point(CGAL::ORIGIN)) - 1;
}
int main()
{
    Mesh_domain domain =
        Mesh_domain::create_implicit_mesh_domain(sphere_function,
                                                 K::Sphere_3(CGAL::ORIGIN, K::FT(2)));
    // Mesh criteria
    Mesh_criteria criteria(params::facet_angle(30).facet_size(0.1).facet_distance(0.025).cell_radius_edge_ratio(2).cell_size(0.1));
    // Mesh generation
    C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria);
    // Output
    std::ofstream medit_file("out.mesh");
    CGAL::IO::write_MEDIT(medit_file, c3t3);
    medit_file.close();
    return 0;
}

使用以下命令编译项目。如果前面没执行xrepo预先安装依赖,并且在sdk目录下没有找到依赖库,会自动提醒你是否通过xrepo下载依赖。

xmake f -c --require=yes # 自动请求依赖
xmake b -v

好在下载了一次之后,后面编译不需要重复下载依赖了。

如果本机上有安装cgal库文件不想通过github下载,可以执行以下命令指定本地搜索路径同时禁用xrepo自动请求依赖功能。不过十有八九会失败,因为xmake找不到本地安装的cgal头文件。

xmake f -c --sdk='D:/opt/msys64/clang64' --toolchain=llvm --pkg_searchdirs='D:/opt/msys64/clang64' --require=no
xmake b -v

感觉xmake的本地库搜索功能可以再改进下。本机上的msys2工具链都安装了cgal,可本地库搜索就是搜不到非要从github上下载……

6. 启用IntelliSense提示

在vscode上使用xmake配置和编译项目之后不会自动启用相关依赖的IntelliSense快速提示,也就是说IntelliSense找不到cgal及其依赖的头文件路径。需要先在.vscoe目录下生成compile_commands.json文件之后,再手动编辑c_cpp_properties.json文件并加上以下代码,方可启用IntelliSense快速提示。

// c_cpp_properties.json
{
    "configurations": [
        {
            "compileCommands": ".vscode/compile_commands.json"
        }
    ],
    "version": 4
}

至于compile_commands.json文件,可以通过在vscode搜索栏中按下Ctrl+Shift+P执行拓展命令XMake: Package来生成。

87324d024f767e6b64798ceb981a639d.png

也可以通过终端执行以下命令生成compile_commands.json文件。

xmake p

7. 编译qt项目(成功)

执行以下命令建立qt项目,基于widget方式建立应用。

xmake create -t qt.widgetapp qwidget_test

查看自动生成的xmake.lua文件,并没有引入qt库文件。

-- xmake.lua
add_rules("mode.debug", "mode.release")

target("qwidget_test")
    add_rules("qt.widgetapp")
    add_headerfiles("src/*.h")
    add_files("src/*.cpp")
    add_files("src/mainwindow.ui")
    -- add files with Q_OBJECT meta (only for qt.moc)
    add_files("src/mainwindow.h")

...

此时编译会弹出错误提示:error: Qt SDK not found!

在target中添加qt6的包引用。

-- xmake.lua
...
add_requires("qt6widgets") -- 通过xrepo下载qt6

target("qwidget_test")
add_packages("qt6widgets") -- 添加qt6的引用
add_rules("qt.widgetapp")
...

通过xrepo下载qt6及其依赖。

xrepo install qt6widgets

等待qt6下载安装完成之后执行配置和编译命令。

xmake f -c -m release
xmake b -v

运行,查看qt界面。

xmake run

4294f94c7103e87e7179e022ec950b5b.png

Windows上默认采用的是msvc的cl编译器,现在切换成clang-cl试试。

xmake f -c -m release --toolchain=clang-cl
xmake b -v

不需要重新下载。

换成static版qt6,更改xmake.lua文件。

-- xmake.lua
...
add_rules("qt.widgetapp_static") -- 改成静态编译版本
...

下载指定版本的qt6。

xrepo install -f "runtime='MT'" -k static qt6widgets

奇怪的是,没有下载任何包文件,也没有报错。

配置编译静态版qt6程序。

xmake f -c -m release --toolchain=clang-cl --runtimes=MT 
xmake b -v

配置过程没有报错,但编译提示链接错误,应该是xrepo没有提供静态编译版qt6的包,看来只能使用自己编译静态的qt6库。

b553d40283015a8fa95d59924a7c87c6.png

从xrepo的官方源列表5上看,没有提示qt6widgets这个包是否有静态版。官方源提供的qt6的模块不是很全,没有看到qt6的webengine模块。

bd1268ca9ca655a810594387a122a1f3.png

8. 编译wxWidgets项目(成功)

建立一个C++工程目录。

xmake create -l c++ wx_test

编辑main.cpp

// src/main.cpp

#include <wx/wx.h>

namespace wxWidgetsHelloWorldExample
{
    class Frame : public wxFrame
    {
    public:
        Frame() : wxFrame{nullptr, wxID_ANY, "Hello World"}
        {
            auto menuFile = new wxMenu;
            menuFile->Append(ID_HELLO, "&Hello...\tCtrl-H", "Help string shown in status bar for this menu item");
            menuFile->AppendSeparator();
            menuFile->Append(wxID_EXIT);

            auto menuHelp = new wxMenu;
            menuHelp->Append(wxID_ABOUT);

            auto menuBar = new wxMenuBar;
            menuBar->Append(menuFile, "&File");
            menuBar->Append(menuHelp, "&Help");
            SetMenuBar(menuBar);

            CreateStatusBar();
            SetStatusText("Welcome to wxWidgets!");

            Bind(wxEVT_MENU, &Frame::OnHello, this, ID_HELLO);
            Bind(wxEVT_MENU, &Frame::OnAbout, this, wxID_ABOUT);
            Bind(wxEVT_MENU, &Frame::OnExit, this, wxID_EXIT);
        }

    private:
        void OnAbout(wxCommandEvent &event)
        {
            wxMessageBox("This is a wxWidgets Hello World example", "About Hello World", wxOK | wxICON_INFORMATION);
        }

        void OnExit(wxCommandEvent &event)
        {
            Close(true);
        }

        void OnHello(wxCommandEvent &event)
        {
            wxLogMessage("Hello world from wxWidgets!");
        }

        inline static const int ID_HELLO = 1;
    };

    class Application : public wxApp
    {
        bool OnInit() override { return (new Frame)->Show(); }
    };
}

wxIMPLEMENT_APP(wxWidgetsHelloWorldExample::Application);

编辑xmake.lua文件。

-- xmake.lua
add_rules("mode.debug", "mode.release")
add_requires("wxwidgets")

target("wx_test")
set_kind("binary")
add_files("src/*.cpp")
add_packages("wxwidgets")
target_end()

使用clang-cl编译器配置、编译,配置过程中会提示确认是否从xrepo上下载wxWidgets包。

xmake f -c -m release --toolchain=clang-cl
xmake b -v

运行程序,查看效果。

xmake run

e78d7efeced700374caa2259dba8605e.png

xrepo上貌似也只有动态链接库版本,没有找到切换静态链接库的方法。

9. 编译gtk4项目(失败)

xrepo上提供了gtk4和gtk+4两个版本,查看代码gtk+4被重定向到gtk4,这里选用gtk4。

6112734d49c8b753d9939454af442100.png

建立一个C工程目录。

xmake create -l c gtk_test

编辑main.c源文件。

// src.main.c
#include <gtk/gtk.h>

static void
print_hello(GtkWidget *widget,
            gpointer data)
{
    g_print("Hello World\n");
}

static void
activate(GtkApplication *app,
         gpointer user_data)
{
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *box;

    window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Window");
    gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);

    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_halign(box, GTK_ALIGN_CENTER);
    gtk_widget_set_valign(box, GTK_ALIGN_CENTER);

    gtk_window_set_child(GTK_WINDOW(window), box);

    button = gtk_button_new_with_label("Hello World");

    g_signal_connect(button, "clicked", G_CALLBACK(print_hello), NULL);
    g_signal_connect_swapped(button, "clicked", G_CALLBACK(gtk_window_destroy), window);

    gtk_box_append(GTK_BOX(box), button);

    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc,
         char **argv)
{
    GtkApplication *app;
    int status;

    app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

编辑xmake.lua文件。

-- xmake.lua
add_rules("mode.debug", "mode.release")
add_requires("gtk4")

target("gtk_test")
set_kind("binary")
add_files("src/*.c")
add_packages("gtk4")
target_end()

配置编译器为msvc的clang-cl,自动下载依赖包,编译。

xmake f -c -m release --toolchain=clang-cl

f2112875adab96ad8a7789ec18a095e1.png

想用msvc编译,但尝试不成功。

134454c656cbdb8a9a8be2a8486d5f32.png

切换msys2的mingw64再尝试下。

xmake f -c -m release --toolchain=mingw --mingw='D:/opt/msys64/mingw64'

bf2978806347bec0fe8b505e7f1854ca.png

mingw64尝试用xrepo下载gtk4,还是不成功。

97cb6e3361b7508f5cac934fbdb3627f.png

无奈,直接搜索本地安装的gtk4。

xmake f -c -m release --toolchain=mingw --mingw='D:/opt/msys64/mingw64' --pkg_searchdirs='D:/opt/msys64/mingw64' --require=no
xmake b -v

好吧,这回连头文件都找不到了……

0de221e675cf475e4ab6752c60f7608f.png

10. 总结

xmake+xrepo实测下来有不少问题,官方源所提供的包质量参差不齐,本地已安装的包添加了搜索路径也找不到,指定gdb、lldb调试器失败等等。 xmake最大的问题在于它太新了,很多细节没有磨合到位。未来等它进一步完善,才会考虑用来管理大型项目。看来未来很长一段时间,windows下的主力开发还是离不开vcpkg和cmake。