作者:庄晓立(Liigo),日期:20230812。

前言:rsync for Windows

因工作需要在 Windows 服务器上部署运行 rsync,可 rsync 官方并不提供 Windows 版本。所以我想着能不能自己编译它的源代码。首先想到的是 WSL2 环境,结果在里面编译出来的是 ELF 文件,并不是 EXE,在 Windows 里根本无法运行。后面又想到了 MSYS2 和 Cygwin。

MSYS2

MSYS2 是运行在 Windows 系统上的一个 Linux 运行环境和编译环境,其编译结果是 Windows 程序。有些 Linux 开源项目官网不提供 Windows 版可执行文件,只能自己下载源码编译,这种情况下 MSYS2 就有用武之地了(注:前提是项目源码是跨平台的,否则 MSYS2 也无能为力)。MSYS2 有终端有 Shell 有各种工具应用,MSYS2 内置了包管理工具 Pacman,通过它可以安装 GCC,安装第三方项目开发库。Pacman 使得安装开源项目的依赖项变得容易。

MSYS2 与 WSL 的区别:MSYS2 里的 gcc 编译出的是 Windows 应用(PE/EXE/DLL),而 WSL 里的 gcc 编译出的是 Linux 应用(ELF/SO)。

Pacman 配置和更新 / 安装 GCC 和第三方库

  • 配置国内源:sed -i "s#https\?://mirror.msys2.org/#https://mirrors.tuna.tsinghua.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist*
  • 更新 MSYS2:pacman -Suy
  • 安装 GCC:pacman -S mingw-w64-ucrt-x86_64-gcc
  • 安装第三方库:pacman -S mingw-w64-ucrt-x86_64-XXX(其中 XXX 为库名称,例如 openssl)

注意安装包都要加上前缀 mingw-w64-ucrt-x86_64-(我被坑过,它为啥不自动加上前缀)。终于明白了,它有多个启动入口(msys2.exe/ucrt64.exe/mingw64.exe/…),不同入口有不同的默认系统前缀。我们应该选择 ucrt64.exe 入口,其默认前缀应该是 mingw-w64-ucrt-x86_64- 不对,它推荐使用且默认启动的就是 UCRT64 环境入口 ucrt64.exe参考)。

在 MSYS2 内编译 rsync 以失败告终

我用了两天时间尝试在 MSYS2 内编译 rsync,折腾出很多问题,不得不放弃,原因最终归结为:rsync 源码本身不跨平台,不支持在 Windows 系统运行,因而无法直接编译 Windows 版本。说实话这事不能怨 MSYS2,巧妇难为无米之炊嘛,MSYS2 是提供了 Windows 的头文件和库,rsync 源码不去调用谁也没招。网上倒是有人提供 rsync 的 Windows 版本下载,估计它们大幅修改了 rsync 源码。(备注:此处理解可能有误。)

用 Pacman 安装 rsync 成功

pacman -S rsync 直接安装,目标文件在 /usr/bin/rsync.exe,大小 535KB,运行时依赖 msys-2.0.dll, msys-crypto-3.dll, msys-lz4-1.dll, msys-iconv-2.dll, msys-xxhash-0.dll, msys-zstd-1.dll 等运行库,exe 和 dll 加起来共计 10MB。这样看来 MSYS 应该也是可以编译 rsync 源码呀(要不然它这个 rsync.exe 哪来的呢)。我前面的理解可能有误。

Cygwin

Cygwin 安装 rsync.exe

前面试过 MSYS2 编译 rsync 失败了,再尝试一下 Cygwin,体验很好。先下载一个安装包只有 1.3MB,安装界面可选国内源(163/aliyun/huawei 等),软件包里有 rsync 3.2.7 可选。一开始我只选了 rsync,其他没选,但它仍默认下载了一些常用包(约 75 个),最后弹出一个终端界面,里面就可以直接使用 rsync.exe 了,真赞。最后看一下安装目录,总计 136MB,bin 子目录内有 bash.exe, cat.exe, cd, chmod.exe, cp.exe, dir.exe, echo.exe, vi.exe 等 Linux 常见命令的 Windows/EXE 版本,另有 cygwin1.dll 等运行库,当然还有我刚才选择下载的 rsync.exe(536KB)。rsync.exe 在 bin 目录下可直接运行,复制到别的目录运行的话,提示缺少如下文件:cygwin1.dll, cyglz4-1.dll, cygiconv-2.dll, cygcrypto-1.1.dll, cygxxhash-0.dll, cygz.dll, cygzstd-1.dll,这些文件都可以在 bin 目录内找到。rsync.exe 和所有依赖的 dll 加起来一共 7.7MB,可以复制到别的 Windows 电脑上运行。

成功编译 rsync 源码

Cygwin 里可以选装 GCC 11.4(gcc-core)和 Make 4.4,再选装 ssl,lz4,zstd,xxhash 开发包,然后就可以编译 rsync 3.3pre1 源码了,./configure, make, 一切顺利,编译得到的 rsync.exe 大小为 2.1MB,同样依赖 cygwin1.dll 等一堆运行库,全加起来共计 9.2MB,经测试功能正常。Cygwin 在此处的表现堪称完美,远超 MSYS2。要知道 rsync 源码本身不支持 Windows,MSYS2 对编译 rsync 束手无策,而 Cygwin 居然可以不动声色地编译出 Windows 版 EXE,堪称神奇。现在反思 MSYS2 的应用场景,似乎挺尴尬,在源码不支持 Windows 的情况下它不起作用,而源码支持 Windows 的情况下却又没必要用 MSYS2 了。我看 MSYS2 官方展示的 CI 场景,在 Windows 容器里安装 MSYS2 编译项目运行测试,其实完全可以抛开 MSYS 直接在 Windows 容器里做同样的工作嘛。(备注:此处对 MSYS2 的理解可能有误,有待验证)。

Msys2 VS Cygwin

我用如下代码做一个实验:

#include <stdio.h>
#include <pthread.h> // to use pthread
#include <unistd.h>  // to use sleep

static void* mythreadproc(void *arg) {
    printf("thread begin, tid=%d\n", (int)pthread_self());
    sleep(*(int*)arg);
    printf("thread end, tid=%d\n", (int)pthread_self());
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    int secs1 = 1, secs2 = 3;
    printf("main start, tid=%d\n", (int)pthread_self());
    pthread_create(&thread1, NULL, mythreadproc, &secs1);
    pthread_create(&thread2, NULL, mythreadproc, &secs2);
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    printf("main end, tid=%d\n", (int)pthread_self());
    return 0;
}

以上代码明显是针对 Linux 编写的,根本没考虑要编译成 Window 的 EXE。编译方法: gcc main.c -pthread。分别在 WSL/MSYS2/Cygwin 编译运行,三者编译运行都正常。除 WSL 生成 Linux 的 ELF 可执行文件外,其余二者均生成了 Windows 的 EXE。这样看来 MSYS2 也不要求源码跨平台呀,奇怪了。

Mingw64 VS Msys2 VS Cygwin

曾经有人通俗的说:Msys2 是轻量版的 Cygwin,是增强版的 Mingw64。对,也不对。

从概念上来说:Mingw64 是 GCC 的 Windows 移植版,它是编译器;Msys2 和 Cygwin 都是 Windows 系统下的 Linux 系统运行环境,它们是类 Linux 系统。历史上先有的 Cygwin,确实也很臃肿,以至于后来诞生了更轻量级的 Msys2,可是后来 Cygwin 自己演进也越来越轻量级,以至于现在跟 Msys2 不相上下(LIIGO 个人感受)。

Mingw64 是编译环境,Msys2 和 Cygwin 是 Linux 运行环境,从概念上论差距很大呀,为什么经常有人把这三者一起谈论一起比较呢?是这样的,Mingw64 是编译环境不假,可编译时也少不了用 cd/ls/make 这些命令吧,于是它为了提升用户体验,内嵌了一个精简版的 Msys2。而 Msys2 和 Cygwin 呢,它们是 Linux 运行环境不假,可 Linux 环境怎能少了 gcc 呢,于是它们为了提升用户体验,也允许选装 Mingw64。你看,Mingw64 里有了 Msys2,Msys2 里有了 Mingw64,可不就越来越像了嘛。

三者之间怎么选择呢?我(Liigo)个人以为首先可以排除单独使用 Mingw64,它仅仅是编译环境,不能很方便的使用第三方开发库,编译源码时总要依赖第三方库嘛,手动下载和配置多费劲呀。所以我会更倾向于使用 Msys2 和 Cygwin,二者既有 Linux 运行环境,又有选装的 Mingw64(gcc),还有库管理器(如 Pacman)用于快速安装第三方库,而且还可以不编译源代码就直接下载可执行文件,非常方便,而且也算不算多重量级,三五百兆而已。Msys2 和 Cygwin 又该选谁呢,见仁见智,我的意见参见下文。

后记:rsync

在 MSYS2 里安装 rsync:pacman -S rsync,在 MSYS2 环境内启动后台 daemon: rsync --daemon --config=rsyncd.conf,配置文件 rsyncd.conf 内容:

port = 9191
use chroot = false
log file = rsync.log

[test]
path = /d/tmp/rsync-test
read only = false

在 MSYS2 外面启动执行居然也可以(方便加到计划任务里),runrsync.bat:

cd D:\msys64\home\Administrator
D:\msys64\usr\bin\rsync --daemon --config=rsyncd.conf
pause

服务端到本地同步:./rsync -a rsync://121.196.13.93:9191/test/ local/ --delete
本地到服务端同步:./rsync -a local/ rsync://121.196.13.93:9191/test/ --delete

备注:Git 自带的 MSYS 里用不了 rsync --daemon,莫名其妙报 chdir failed(难道是因为它没用 home 没有 user?),折腾一天浪费了很多时间。后来切换到官方的 MSYS 就很顺利搞定。

备注:要是按前面的使用感受,综合下来应该是 Cygwin 优于 Msys2,那为什么我最后还是选用了 Msys2 呢?我想其中关键的一点是 Msys 访问磁盘路径更贴心:/c,而 Cygwin 就糟心一些:/cygdrive/c—— 没想到在一个小细节上决定了胜负。Msys2 还有一个优势,是可以编译出不依赖任何 msys-***.dll 的纯正的 EXE,而 Cygwin 编译出来的 EXE 是没办法不依赖 cyg***.dll 的。当然 Cygwin 也有自己的重大优势,兼容性更强,可以直接编译在 Msys2 不能直接编译的源代码(例如上文提及的 rsync)。