使用 archlinux 有一段日子了,发现有时候在使用 wine 的过程中,明明已经装了 wine-mono 这个包,但依然时常出现这个对话框,很恼火,是时候把这问题探究彻底了。
问题描述
我们知道,wine 的 mono 组件是.NET Framework的开源和跨平台实现,能够让 Wine 顺利运行很多 .NET应用程序。
出现上述对话框后,点击安装,虽然会自动从 wine 官网把 mono 组件下到 $WINEPREFIX 里面,也是可以用,但是下的很慢,耗费很多时间,而且用 .NET 程序用的少,有时候仅仅想测试一个 exe 程序,点取消的话,每次要 wine 一个程序的时候都会弹出这个,实在讨厌。
后来,我觉得最新版的 wine 没有 wine-stable 稳定,我自己从 aur 编译了 wine-stable 之后,开始次次出现以上对话框了,经过百度谷歌,搜到的全是解决别的问题,无奈之下只能自己动手丰衣足食。
问题探究
先看看 wine-mono 这个包包含什么。
1 | pacman -Ql wine-mono |
根据输出结果看出,原来只包含一个文件,还带版本号 /usr/share/wine/mono/wine-mono-4.9.3.msi
emmmm,什么?带版本号?我想起这问题偶然出现,什么时候出现呢,就是升级系统的时候会偶尔出现。那么 wine 是如何找到这个文件,以确认 mono 存在性呢?难道是扫描 /usr/share/wine/mono/ 整个目录?那可以找到的啊。
根据这些线索,我猜测,wine 是根据绝对路径和带版本号的文件名寻找 mono 的,一旦所需要的 mono 版本,和系统中存在的 mono 版本不一样,就会出现那找不到的对话框。
如何验证这个猜想呢,我想到 grep -rn 这个在所有子目录里面查找匹配的功能,开始行动。
先查看 wine-stable 包含哪些文件
1 | pacman -Ql wine-stable |
根据输出结果,大概知道 wine 的文件集中在以下几个目录
1 | /usr/lib/wine |
还有很多细小的目录,直接搜会很麻烦。我想到一个方案,把以前编译好的 wine-stable 安装包解压到一个目录里面,集中搜索。说干就干!
1 | mkdir /tmp/wine |
然后开始搜索
1 | grep -rn wine-mono |
几秒钟后,输出结果只有三行
1 | 匹配到二进制文件 usr/lib32/wine/appwiz.cpl.so |
最后一行保存的是软件包的信息,wine 启动的过程中肯定是不会用的。那…重点研究下 usr/lib/wine/appwiz.cpl.so 这个文件好了
1 | grep -a wine-mono usr/lib/wine/appwiz.cpl.so |
输出结果
1 | 2.47wine_gecko-2.47-x86_64.msigeckoMSHTMLGeckoUrlGeckoCabDir4.7.5wine-mono-4.7.5.msimonoDotnetMonoUrlMonoCabDir%s does not exist and could not be created: %s |
果然,包含了字符串 wine-mono-4.7.5.msi,也就是说,wine-stable 是依赖于 4.7.5 版本 wine-mono,而我系统里存在的是 wine-mono-4.9.3.msi,找不到很正常。
好的,现在做个实验,把 wine-mono-4.9.3.msi 复制为 wine-mono-4.7.5.msi,问题是不是解决了。
1 | cd /usr/share/wine/mono |
发现,确实没再弹出那个对话框了,猜想成立!
终于找到原因了,接下来想想怎么完美解决这个问题吧。
解决方案
我想到几个方案来解决这个问题:
- 安装旧版本的 wine-mono 的包。
- 更新 wine 到最新版本,确保所需的 mono 版本与官方仓库最新的 mono 的版本一致。
- 对 appwiz.cpl.so 这个文件进行二进制编辑,修改里面的版本号字符串。
- 把系统里的 wine-mono-4.9.3.msi 重命名 wine-mono-4.7.5.msi
- 创建符号链接解决这个问题。
想出了这么多办法,逐一分析这些办法的利弊:
- 安装旧版本的 wine-mono 包:这需要降级软件包,需要用 downgrade,然后可以把 wine-mono 这个软件包设为忽略更新的包写到 pacman.conf 里,这样做的话,每次升级 wine-stable 都需要检查版本号手动装合适的 wine-mono,有些麻烦。
- 更新 wine 到最新版本:我大量实践过程中,感觉 wine-stable 确实要稳定一些,最新版的虽然有新特性但是免不了很多 bug。而且,就算更新到最新,也会偶尔出现版本不匹配的问题。
- 对 appwiz.cpl.so 这个文件进行二进制编辑:这个操作很骚,但是万一以后版本号长度不一样了,怎么办呢,而且每次更新都得改也很麻烦。
- 重命名系统里的 wine-mono:这个操作会影响到系统里包含的文件,而每次升级之后,旧的 wine-mono 文件就不会被删掉,然后装上了新的,白白占用空间,每次来处理,也很麻烦。
- 创建符号链接:创建符号链接,可谓是 linux 解决这类问题最妙的办法,直接将 wine-mono-4.9.3.msi 链接到 wine-mono-4.7.5.msi,非常方便,但是每次更新还是要过来处理,还是很麻烦。
可见创建符号链接是目前最低成本的解决方法,那,有没有完美的解决方法呢?
完美解决(已失效)
通过以上思考,我最需要的就是,更新后不需要手动去创建符号链接,用脚本自动实现更新后的解决版本不一致的问题。
每次更新后,更新什么?更新 wine-stable 或 wine-mono 的时候。
如何在每次更新这两个包,触发调用脚本呢?利用包管理器的 hook 功能。
看来完美的解决方案是存在的,下面来列出,需要解决的几个子问题:
- 脚本如何读取 appwiz.cpl.so 这个文件来获取所需的版本号呢?
- 脚本如何确定当前系统存在的 wine-mono 的版本号对应的文件呢?
- 升级后旧版本留下的符号链接会多余存在很多垃圾要怎么办呢?
然后这些问题逐一得到解决:
-
直接利用正则表达式匹配
1
sed -n 's/.*\(wine-mono-[[:digit:].]\+.msi\).*/\1/p' /usr/lib/wine/appwiz.cpl.so
输出结果为 wine-mono-4.7.5.msi
-
直接用包管理器查询包含的文件,然后正则匹配到具体文件名
1
pacman -Qlq wine-mono | grep -o 'wine-mono-\([[:digit:].]\+\).msi'
输出结果为 wine-mono-4.9.3.msi
-
每次更新后,先将 /usr/share/wine/mono 里的符号链接全删了,再建立即可。
将以上思路进行具体实施,写成 hook 脚本,得到完美解决。具体 hook 的写法参见 alpm-hooks文档 。
在 /etc/pacman.d/hooks 里面新建一个文件 wine-mono-version-fix.hook
里面写入
1 | [Trigger] |
至此,完美解决,以后无论如何更新 wine 或 wine-mono,或者无论如何更换 wine 的版本,总是能找到对应的 wine-mono,也再也不会弹出那个对话框了。
后来发现 wine-gecko 也出了类似的情况,那么同理。
在 /etc/pacman.d/hooks 里面新建一个文件 wine-gecko-version-fix.hook
里面写入
1 | [Trigger] |
后续完美解决
上述方法成功维持了一段时间,但最近发现又蹦出那个对话框,上述方法失效了?经过探索发现,/usr/lib/wine/appwiz.cpl.so 这个文件已经被改动,里面的相关字符串已经成了 unicode 字符串,并且文件名多了个 -x86,例如:
1 | wine-mono-5.0.0-x86.msi |
那么,根据这种情况改进一下即可解决。
-
利用正则表达式匹配
1
strings -eb /usr/lib/wine/appwiz.cpl.so | sed -n 's/.*\(wine-mono-[-x[:digit:].]\+.msi\).*/\1/p'
输出结果为 wine-mono-5.0.0-x86.msi
-
用包管理器查询包含的文件,然后正则匹配到具体文件名
1
pacman -Qlq wine-mono | grep -o 'wine-mono-\([-x[:digit:].]\+\).msi'
输出结果为 wine-mono-5.0.0.msi
写成 hook 脚本 wine-mono-version-fix.hook
1 | [Trigger] |
同理,gecko 也这样解决。
1 | [Trigger] |
最后,我将上述两个文件用 PKDBUILD 打包上传到 AUR,方便后续使用,包名为 wine-mono-gecko-version-fix