原来习惯了在Windows下使用cmake+vcpkg编译C/C++程序,但是vcpkg有个缺点,每次更新都需要从github下载源码编译库文件。像qt那样的大型库,每次光是编译动不动就浪费一整天时间实在耗不起,更别说DNS导致的连接问题了。
先梳理下需求,对C/C++包管理器的要求主要以下3点:
- 支持gtk+、qt、wxWidgets、cgal、vtk等常用库。
- 支持跨平台和IDE集成,或者支持CMake。
- 及时更新。
能满足条件的除了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
vscode可以安装以下拓展,用于查看和构建xmake项目。
- XMake - vscode的xmake项目支持插件
- vscode-lua-format - lua代码格式化工具,可选
2. 创建项目
可以直接使用xmake的create命令创建项目,默认为C++项目。
xmake create hello_cpp
也可以增加-l开关指定项目的语言类型。
xmake create -l c hello_c
打开hello_c项目自动生成的xmake.lua文件,后面一大段注释告诉用户怎么配置、构建和安装项目。直接在项目文件夹中执行xmake命令即可编译。
使用vscode安装了拓展的话,也可以通过下方的按钮执行编译命令。
在vscode右侧的拓展窗口可以配置编译用的工具链、平台和调试信息。
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来生成。
也可以通过终端执行以下命令生成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
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库。
从xrepo的官方源列表5上看,没有提示qt6widgets这个包是否有静态版。官方源提供的qt6的模块不是很全,没有看到qt6的webengine模块。
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
xrepo上貌似也只有动态链接库版本,没有找到切换静态链接库的方法。
9. 编译gtk4项目(失败)
xrepo上提供了gtk4和gtk+4两个版本,查看代码gtk+4被重定向到gtk4,这里选用gtk4。
建立一个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
想用msvc编译,但尝试不成功。
切换msys2的mingw64再尝试下。
xmake f -c -m release --toolchain=mingw --mingw='D:/opt/msys64/mingw64'
mingw64尝试用xrepo下载gtk4,还是不成功。
无奈,直接搜索本地安装的gtk4。
xmake f -c -m release --toolchain=mingw --mingw='D:/opt/msys64/mingw64' --pkg_searchdirs='D:/opt/msys64/mingw64' --require=no
xmake b -v
好吧,这回连头文件都找不到了……
10. 总结
xmake+xrepo实测下来有不少问题,官方源所提供的包质量参差不齐,本地已安装的包添加了搜索路径也找不到,指定gdb、lldb调试器失败等等。 xmake最大的问题在于它太新了,很多细节没有磨合到位。未来等它进一步完善,才会考虑用来管理大型项目。看来未来很长一段时间,windows下的主力开发还是离不开vcpkg和cmake。