为Rust+QT
编程搭建【伪】win32
开发环境
细心的读者一定会注意到文章标题内的【伪】字。哎!实属无奈。Rust Qt Binding对win32
编译环境实在不友善。在windows
操作系统上,qmetaobject始终编译失败 --- 这是一个已备案的缺陷。幸运的是,我本地操作系统是Windows 10 x64
,所以还有一条“曲线救国”的“狗血”技术路线。概括地讲,
- 在
Windows 10 x64
上,安装Ubuntu-20.04
子系统(绝不是VM
,而是WSL2
)。即是传说中的Windows Subsystem for Linux 2
。 - 在
Ubuntu-20.04
子系统内,安装全套的rustup
工具链Qt SDK
- 题外话,即便安装一个
nwjs for Linux
,其也是能够正常运行的。
- 将
Windows 10 x64
上的VSCode
远程连接至Ubuntu-20.04
。这样一来,- 既将
VSCode
人机交互界面继续保留在Windows
端 --- 开发体验不降级。 - 又将
rustc
编译与Qt link
都迁移至Linux
端执行 --- 绕开qmetaobject对windows
编译环境的不兼容。
- 既将
- 将
Ubuntu-20.04
的图形界面投影至Windows 10 x64
。以便,能够在windows 10
环境上,观察GUI
程序的修改效果 --- 这还是为保持开发体验不降级。 - 在功能开发结束之后,在
target/release
和target/debug
目录下的就是Linux
版的GUI
应用程序分发包。 - 需要借助【交叉编译】,才能在
target/x86_64-pc-windows-msvc
文件夹内,获得Windows
版的GUI
应用程序分发包。
下面就是这一出既“狗血”又超级麻烦的自虐之旅的详细内容。希望世界变得更平,别让我这样的Windows
狗活得那么辛苦。
为什么选择QT
?
前不久,我写的另一篇文章为Rust
原生gui
编程,搭建win32
开发环境分享了如何将Rust
链接Gnome.GTK3
实现GUI
应用程序开发。对照两款图形界面解决方案,Gnome.GTK3
上手容易,文档齐备,社区活跃。但是,QT
强烈吸引我的有两点:
- 覆盖全平台。
- 从【桌面】,到【移动端】,再到【嵌入式设备】
- 从
Windows
,到Linux
与Mac
,再到Android
与iOS
- 虽然眼下
Rust Qt Binding
对windows
操作系统的兼容性还有不足,但我相信这仅只是临时缺陷,会被尽快修复。
- 虽然眼下
- 可移植性强。
- 至少它依赖的动态链接库里没有与
win32
内置dll
重名的。Gnome.GTK3
的这个毛病可是把我给恶心到了。我之前可是下了佬大的时间与精力才定位出此crash
的原因。
- 至少它依赖的动态链接库里没有与
我甚至有一个不成熟的想法:“QT
才是前端开发者最终的技术归宿”,因为它:
- 全平台
- 高性能
- 亲和
IoT
--- 还是有相当多的IoT
设备是拥有独立屏显的。不可能在所有的IoT
硬件上都运行一个web service
等着别人用浏览器访问(这不是高端玩家的作法)。
不过在这里,我还是想分别对Gnome.GTK3
与QT
分别吐槽三点:
Gnome.GTK3
对·新人上手·真是没得说、太棒了。但是,其【高级组件】(比如,webview
)不支持windows
平台太挫伤开发者的深度使用热情了。QT
几乎无所不能。但是,Rust Qt Binding
对win32
编译环境的适用性太差。这对普通玩家的入手门槛有些高了。- 最后,上面两个问题都不是新问题,而都是陈年老梗了。官方怎么对这些缺陷的解决这么不上心呀?
为什么选择qmetaobject?
- qmetaobject是
QT
官方团队维护的项目。我相信其后续迭代有保障。QT
官宣软文看这里。 - 你不会以为其它的
Rust Qt Binding
解决方案对win32
编译环境友善吧?Naively! 事实上,以下几款Rust Qt Binding
开源项目,我都试过了。它们中没一个对win32
友好的。此时,我脸上只有一个表情就是“悲愤”。世界一点儿也不平!
劝退理由
整个开发环境包括子系统Ubuntu-20.04 (WSL)
和宿主Windows 10 x64
两个部分。其对开发环境的软件与硬件条件都有一点儿要求:
- 低版本的
Windows
操作系统不具备WSL2
功能。所以,如果你的操作系统是Windows 7
,推荐先研究一下如何免费地升级到Windows 10
。 - 从微软应用程序市场下载安装的
Ubuntu-20.04 (WSL2)
仅只是一个Linux Kernel
(大约1.2GB
)。所以,后续需要安装的软件包非常地多。你的系统盘足够大吗?若系统盘的存储余额小于25GB
,推荐先研究一下【系统盘】“搬家”以腾出足够的空间折腾。
开发环境搭建概述
被用来验证开发环境的Rust + QT
工程是来自官方的演示例程todos
整个安装配置大约是以下若干步:
- 安装与配置
Ubuntu-20.04 (WSL2)
- 配置宿主端
Windows 10
- 回到
Ubuntu-20.04 (WSL2)
安装Qt SDK
。因为QT
的连线安装程序是GUI
的,所以必须先在第二步解锁【从WSL2
子系统向宿主系统投影图形界面】的技能。 - 从
Ubuntu-20.04
端,使用VSCode
打开todos工程。 - 从
VSCode
集成终端,执行cargo run
弹出应用程序窗口,验证开发环境正常工作。
开发环境搭建之子系统端 - Ubuntu-20.04 (WSL2)
安装Ubuntu 20.04 (WSL2)
子系统
我给WSL2
子系统安装Ubuntu
不是因为Ubuntu
有什么独一无二的技术优势;而仅是,相对于CentOS
,Ubuntu 20.04
子系统不要钱。在微软应用程序商店,除了Mac
以外(知识产权保护)的桌面系统都有WSL2
镜像安装包。它们之间最大的差别就是【收费】与【免费】。
再次劝退
Windows Subsystem for Linux 2
会被强制安装于系统C
盘。所以,在安装Ubuntu 20.04 (WSL2)
子系统之前,请先确认你的系统盘是否还有足够的剩余空间。在我的使用场景里,子系统满载体量大约14GB
(以后,可能还会更大)。虽然WSL2
既好看又好用,但它是有成本的。
Ubuntu 20.04 (WSL2)
子系统与Windows 10 x64
宿主系统之间的关系
- 硬盘关系
- 子系统的硬盘对宿主系统是不可见。
- 宿主系统的硬盘是被逐个
mounted
到Ubuntu 20.04 (WSL2)
【挂载点】/mnt
虚拟文件夹下,所以宿主系统文件系统对子系统皆是可见的,且被视作外部存储设备。
- 环境变量关系
- 子系统会继承(甚至,重写)宿主系统的全部环境变量 --- 这个潜在地造成了混乱,我后面会给出规避方式。
- 宿主系统访问不到子系统内的环境变量。
- 程序执行关系
- 子系统可运行宿主系统的
.exe
文件。比如,ipconfig.exe
--- 后面会用到。 - 宿主系统既看不到,也执行不了子系统的可执行文件。
- 子系统可运行宿主系统的
安装步骤
-
以【管理员】身份运行
PowerShell
-
激活
Windows Subsystem for Linux 2
功能dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
-
下载与安装Linux Kernel 更新包
-
将
WSL2
设置成为默认启动项wsl --set-default-version 2
-
从【微软应用程序商店】搜索Ubuntu 20.04 LTS并安装之。除了
Ubuntu
外,还提供有CentOS
子系统。但,这是收费的。
WSL2
子系统基本操作
-
开机
wsl -d Ubuntu-20.04
在第一次启动
Ubuntu 20.04 LTS
子系统时,你会被要求创建一个管理员账号。根据提示,你照做就是了。 -
关机
wsl --shutdown # 或 wsl -t Ubuntu-20.04
-
打开运行中子系统的终端
wsl
-
列出所有已安装的子系统
wsl -l -v
上面所有操作在cmd
和PowerShell
均可完成。但,我还是推荐安装windows terminal。
配置Ubuntu 20.04 (WSL2)
子系统
启动Ubuntu 20.04 (WSL2)
子系统
打开cmd
或PowerShell
终端,执行
wsl -d Ubuntu-20.04
在第一次启动Ubuntu 20.04 LTS
子系统时,你会被要求创建一个管理员账号。根据提示,你照做就是了。
软件包安装
sudo apt-get install -y build-essential libfontconfig1 mesa-common-dev libglu1-mesa-dev libxkbcommon-x11-0 libwayland-cursor0 net-tools curl libxcb-icccm4 libxcb-image0 libxcb-shm0-dev libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcursor1 mingw-w64 desktop-file-utils libnss3-tools libcups2-dev libgconf-2-4 libpangocairo-1.0-0 libxss1 libatk1.0-0 libgtk-3-dev fonts-arphic-ukai fonts-arphic-uming language-pack-zh* chinese* ruby ruby-dev
上面这些软件包涉及了:
gcc
编译工具链- 代码编辑工具
- 版本控制工具
- 图形界面基础库
- 交叉编译(面向
windows
操作系统) - 中文字符集
等等吧。可谓是一应俱全。也足以支持nwjs for Linux
正常运行。
配置vi
编辑器
虽然VSCode
能够直接编辑Ubuntu 20.04 (WSL2)
子系统内任何文本文件,但是vi
仍旧是一款很有仪式感且使用便捷的文本编辑器。
vi ~/.vimrc
# 添加如下内容
set textwidth=200
set shiftwidth=2
set tabstop=2
set expandtab
set nu
set autoindent
set cindent
set fileencodings=utf-8
set termencoding=utf-8
set encoding=prc
# 并保存退出
重置环境变量
默认情况下,WSL2
子系统会继承宿主操作系统的环境变量。我遇到多次因为Ubuntu 20.04 (WSL2)
少装了软件包,而误载入了同名的Windows 10
宿主动态链接库的情况。这类问题一旦出现很难定位原因,错误日志的内容也很让人费解。解决起来好是耽误功夫!于是,我才总结出这么一条来。将它们“切隔”得泾渭分明。
vi ~/.bashrc
# 在文件的最顶端,添加
export PATH="/usr/local/sbin:/usr/local/bin";
export PATH="$PATH:/usr/sbin:/usr/bin";
export PATH="$PATH:/sbin:/bin";
export PATH="$PATH:/usr/games:/usr/local/games";
export PATH="$PATH:/snap/bin";
# 保存文件和退出文件
source ~/.bashrc
# 给宿主端的 VSCode 启动脚本创建符号链接。
# - 你本地的 VSCode 安装目录,可能有所不同,注意替换。
sudo ln -s '/mnt/c/Program Files/Microsoft VS Code/bin/code' code
另一方面,倘若你真的有必要在WSL2
子系统里执行宿主系统的.exe
文件(比如,执行ipconfig.exe
来获得宿主系统的ip
地址):
-
要么,直接写绝对地址
-
要么,在
/snap/bin
目录下,创建符号链接sudo ln -s /mnt/c/Windows/System32/ipconfig.exe /snap/bin/ipconfig.exe
千万不要改PATH
环境变量,因为PATH
仅只对目录有效,一次至少会导入一个文件夹的动态链接库,简直是后患无穷。在本次开发环境搭建过程中,涉及到的符号链接只有两个:
ipconfig.exe
- 下文会提到使用它来实时地获取宿主操作系统的ip
地址。VSCode
的启动脚本文件${VSCode_安装目录}/bin/code
- 以便从子系统端打开一个工程和编辑代码。
安装node.js
先安装nvm
版本管理器,再安装node
,npm
与pnpm
。
提示:安装脚本可能需要“科学上网”才能被下载。所以,
export http_proxy=
与export https_proxy=
两个环境变量没准需要你配置好,以指向某个有效的地址。
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
source ~/.bashrc
nvm install 10.24.1
nvm use 10.24.1
nvm alias default v10.24.1
npm i -g pnpm
安装ruby
依赖包
sudo gem install compass
sudo gem install sass
安装rustup toolchain
提示:工具链模块可能需要“科学上网”才能被下载。所以,
export http_proxy=
与export https_proxy=
两个环境变量没准需要你配置好,以指向某个有效的地址。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# `nightly`频道最新版本的`rustup toolchain`不兼容于`Rust Qt Binding`,所以再安装老一点的版本。
rustup toolchain install nightly-2021-03-25-x86_64-unknown-linux-gnu
# 这一步理论上是可选的,因为`cargo`工程能够在工程层级重写`rustup toolchain`的版本指向。
rustup default nightly-2021-03-25-x86_64-unknown-linux-gnu
# 向 windows 10 x64 交叉编译时,用得上。
rustup target add x86_64-pc-windows-msvc
vi ~/.bashrc
# 在文件的最尾端,添加
# - 调试程序时,可看得见完整的调用栈
export RUST_BACKTRACE=1;
# 保存文件和退出文件
source ~/.bashrc
到写这篇文章时止,qmetaobject与最新版的nightly-x86_64-unknown-linux-gnu
工具链还有兼容性问题(编译报错),所以我本地安装的nightly rustup toolchain
还是2021
年3
月的版本。借助我所做的另一款开源工具rustup-components-history,你能找到更多的nightly rustup toolchain
的历史版本。毕竟,官方给出的历史版本可用性清单仅提供最新7
天的信息。
大家看,我的相关功课做得全吧?是不是,无处不惊喜?所以,请帮忙,多转发,多参阅。
配置XSTATA
向宿主系统投影图形界面
题外话,
XSTATA
与X11 Server
是个啥子关系?虽然官方文档与名称暗示都是将
X11 Server
摆在了【后端】的位置上,但真实情况正好是对调的。X11 Server
负责收集使用者的键盘与鼠标输入,像是个网页;而XSTATA
居中调度,完成图形计算与给出反馈,像是个web service
。
在这段配置里有一个痛点。即,WSL2
子系统需要明确地知道X11 Server
所在的Windows 10
宿主操作系统的ip
地址。请不要自做聪明地认为127.0.0.1
可能搪塞过去。127.0.0.1
是指向Ubuntu 20.04 (WSL2)
自身的 --- 咱们一定得把WSL2
子系统与宿主看作是两个独立的workstation
。若你的电脑正在连接一个DHCP
路由器,那么情况会更糟一些,因为电脑的ip
地址会经常地变化。而反复地修改Linux
配置文件往往是各种坏事情的开端。
为了缓解这个痛点,我采取的措施包括两步:
-
在
/snap/bin
目录下,给C:\Windows\System32\ipconfig.exe
创建一个符号连接/snap/bin/ipconfig.exe
sudo ln -s /mnt/c/Windows/System32/ipconfig.exe /snap/bin/ipconfig.exe
-
在
$HOME
目录下,编写了一个.get_hot_ip.js
脚本程序,来- 执行
/snap/bin/ipconfig.exe
命令 - 解析
/snap/bin/ipconfig.exe
在标准输出打印的文本内容 - 从输出内容里扣出宿主系统的
ip
地址 --- 需要一点儿简单的正则匹配 - 打印此
ip
地址字符串至标准输出
- 执行
然后,就可以简单地修改~/.bashrc
文件了:
vi ~/.bashrc
# 在文件的最尾端,添加
export LIBGL_ALWAYS_INDIRECT=;
# 执行 js 文件获取 ip 地址
export DISPLAY="$(~/.get_hot_ip.js):0.0";
# 保存文件并退出
source ~/.bashrc
于是,即便DHCP
路由器时不时地改变了我们的ip
地址,咱们只要source ~/.bashrc
一下,新ip
地址就生效了。多亏了有nodejs
,要是用shell
来写这个字符串解析程序,那得是多费劲呀。
配置显示中文(包括命令行与GUI
)
sudo locale-gen zh_CN.utf8
sudo update-locale LANG=zh_CN.utf8
然后,关闭当前终端和打开一个新终端,语言切换便随之生效了。另外,敲入命令locale
也能查阅当前的【语言本地化】配置。
开发环境搭建之Windows
宿主系统端
安装X-Server for WinNT
这是将Ubuntu 20.04 (WSL2)
子系统的图形界面投影至Windows 10
宿主系统的关键组件。虽然它的名字叫X11 Server
,但就它的功能而言,其更像是B/S
架构中的网页端。与X-Server
呼应的、在Ubuntu 20.04 (WSL2)
子系统内的Client
端程序是XSTATA
。
- 下载与安装VcXsrv
- 设置【高
DPI
显示模式】- 打开
VcXsrv
安装目录 - 鼠标右键点击
xlaunch.exe
文件。 - 点击【属性】菜单项
- 在【属性】对话框内,切签至【兼容性】选项卡
- 点击【更高
DPI
设置】按钮 - 在新对话框内,选中【替代高
DPI
缩放行为】复选框。 - 在【缩放执行】下拉框内选择【系统(增强)】
- 最后,一路【确定】下来。
- 打开
启动X-Server for WinNT
生成启动配置文件
- 双击
VcXsrv
安装目录(下文记作:%VCXSRV_HOME%
)内的XLaunch.exe
文件 - 打开
Display Settings
配置窗口- 选择
Multiple Windows
(就是左一项) - 在
Display number
数字录入框内,输入0
- 点击【下一页】按钮
- 选择
- 跳转至
Client startup
配置窗口- 选择
Start no Client
- 点击【下一页】按钮
- 选择
- 跳转至
Extra Settings
配置窗口- 选中
Clipboard
- 选中
Primary Selection
- 选中
- 取消选中
Native opengl
- 选中
Disable access control
- 点击【下一页】按钮
- 选中
- 跳转至
Finish configuration
配置窗口- 点击按钮
save configuration
,保存配置为文件%VCXSRV_HOME%\x11-server.xlaunch
- 点击【取消】按钮。
- 点击按钮
将配置文件应用于快捷方式
-
鼠标右(键)击
XLaunch
快捷方式 -
在【属性】对话框中,修改【目标】输入框的内容为
"%VCXSRV_HOME%\xlaunch.exe" -run "%VCXSRV_HOME%\x11-server.xlaunch"
-
点击【确定】按钮
启动X11 Server
双击新XLaunch
快捷方式
配置VSCode
- 安装插件
- 在【用户配置】文件
%AppData%\Code\User\settings.json
里,添加一个新配置项"http.proxySupport": "off"
。否则,在WSL2
模式下,安装扩展插件容易遭遇下载网络失败。 - 关闭当前
VSCode
实例 - 从
Ubuntu-20.04(WSL)
终端,敲入指令code .
。在WSL2
模式下,启动另一个VSCode
实例。 - 给运行于
WSL2
模式下的VSCode
实例安装三个rust
插件
给Ubuntu-20.04 (WSL2)
子系统安装Qt-5.12.11
准备工作
- 在
Windows 10 x64
宿主系统,双击XLaunch
快捷方式启动X11 Server
服务,准备接受来自WSL2
子系统的图形界面投影。 - 在
Ubuntu-20.04
终端命令行,执行source ~/.bashrc
确保之前的配置修改皆奏效。
安装框架与组件库
-
打开
Ubuntu-20.04 (WSL2)
命令行终端wsl
-
在
Ubuntu-20.04 (WSL2)
命令行终端,执行如下命令,下载与启动【Qt
连线安装器】wget https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run chmod +x qt-unified-linux-x64-online.run ./qt-unified-linux-x64-online.run
-
于是,在
Windows 10 x64
宿主端,Qt
安装向导对话框就会被弹出。此时,若完全安装Qt-5.12.11
框架和组件库,则需要几十个GB
的空间。我推荐:- 只安装
Desktop gcc 64-bit
与Qt WebEngine
(webview
这是刚需)就足够了。 - 选择安装目录为
$HOME/Qt
。
- 只安装
配置环境变量
vi ~/.bashrc
# 在文件的最尾端,添加
export PATH="$HOME/Qt/5.12.11/gcc_64/bin:$PATH";
export LD_LIBRARY_PATH="$HOME/Qt/5.12.11/gcc_64/lib:$LD_LIBRARY_PATH";
# 保存文件并退出
source ~/.bashrc
验证Qt
安装成功
执行如下脚本,启动Qt
的图形界面开发工具Qt Creator
。
~/Qt/Tools/QtCreator/bin/qtcreator.sh
验证Ubuntu-20.04 (WSL2)
开发环境
- 检查桌面右下角的托盘,确认
X11 Server
是否已经启动。 - 克隆工程从
github
至本地git clone https://github.com/woboq/qmetaobject-rs.git
。- 这一步发生在宿主系统或
Ubuntu-20.04 (WSL2)
子系统不重要。
- 这一步发生在宿主系统或
- 在
WSL2
命令行终端,cd
到工程根目录/examples/todos
- 输入指令
code .
,回车。以WSL2
模式,启动一个VSCode
实例。 - 打开
VSCode
集成终端,敲入cargo run
命令,开始编译/运行程序。- 另一方面,若你本地已经配置好了
CodeLLDB
插件与编写了正确.vscode/lanuch.json
,直接F5
开始断点调试就更酷了。
- 另一方面,若你本地已经配置好了
如果一切正常的话,此时此刻
VSCode
代码编辑器是在宿主环境Windows 10 x64
。请继续你之前的编程习惯。是不是很舒服?cargo run
使用的是Ubuntu-20.04(WSL)
子系统的gcc
工具链- 甚至,包括更高级的
F5
断点调试也都是走的Ubuntu-20.04(WSL)
子系统gcc
工具链
- 甚至,包括更高级的
todos
图形窗口则是从Ubuntu-20.04(WSL)
,透过XSTATA -> X11 Server
,被投影到宿主Windows 10 x64
操作系统的。
交叉编译
就目前的Windows
编译工具链现状而言,若想支持向Windows
平台分发应用程序软件包,这一步还是绕不过去的。我相信这个情况后续一定会有改善的。这个事已经在github
上向官方技术团队备案了,且已经被受理,和正在处理中。
首先,要明确【rust
交叉编译】是有限制的。即,rustup
工具链的nightly
频道对【交叉编译】都不可用。我们仅能使用stable rustup
工具链。在这,你是不是也感觉心拔凉拔凉的?
然后,咱们再讲怎么搞【交叉编译】。
-
在上文中的【
rustup toolchain
安装】章节,咱们已经预安装了x86_64-pc-windows-msvc``target
。- 此
target
可不认识nightly rust
语法,会编译报错的。
- 此
-
除此之外,我们还要准备一套
Qt for Windows
。- 直接在你的宿主端
windows 10 x64
上,下载与运行Qt for Windows 连线安装向导 - 勾选安装
Qt 5.12.11 -> MSVC 2017 64-bit
。 注意:我勾选安装MSVC 2017 64-bit
是因为我本地已经有Visual Studio 2017
了。 请先确定你自己电脑的预装了哪一版的Visual Studio
再合理地选择Qt MSVC
类型。
- 直接在你的宿主端
-
接着,仅交叉编译时,临时配置环境变量
QT_INCLUDE_PATH
与QT_LIBRARY_PATH
。- 前者,
export QT_INCLUDE_PATH=/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/bin
- 后者,
export QT_LIBRARY_PATH=/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/lib
因为
QT_INCLUDE_PATH
与QT_LIBRARY_PATH
优先级高于PATH
与LD_LIBRARY_PATH
,所以后续的【交叉编译】会优先链接QT for Windows
,而不是开发时依赖的QT for Linux
。 - 前者,
-
最后,
cd
到工程根目录/examples/todos
和执行命令cargo build --target=x86_64-pc-windows-msvc
。完成。rust
自身的交叉编译就是这么简单得,不要,不要的。复杂度都集中在它所链接的cpp
链接库上了。
增补篇:将整个Ubuntu-20.04(WSL)
桌面投影至Windows 10
宿主系统
更多软件包安装
sudo apt-get install -y ubuntu-desktop apt-transport-https systemd-genie unzip imagemagick
允许当前账号·免密·运行systemd-genie
在编辑sudoer
过程中,请将下文中的account_name
替换成你的真实账号名(即,echo $USER
的值)。
sudo visudo --file /etc/sudoers.d/$USER
# 添加如下一行内容
account_name ALL=(ALL) NOPASSWD:/usr/bin/genie
# 保存与退出:`Ctrl + o`,`Ctrl + x`。
若你想图省事,不防试试下面这条一步到位的指令。不过,这就少了一次熟悉与练习visudo
编辑器用法的机会。
echo "$USER ALL=(ALL) NOPASSWD:/usr/bin/genie" | sudo EDITOR="tee" visudo --file /etc/sudoers.d/$USER
若是不小心把sudoer
给改坏了,也不用着急。
- 首先,从
win32
命令行执行wsl -u root
以超级用户登录Ubuntu-20.04(WSL)
- 然后,强制编辑
/etc/sudoers.d/$USER
文件,纠正错误配置记录。
千万用不着,为此,重装整个Linux
子系统 --- 这算不上一次事故。
向apt-get
源清单添加Microsoft
站点
# 进入管理员模式
sudo --shell
# 安装 Microsoft 站点公钥
apt-key adv --fetch-keys https://packages.microsoft.com/keys/microsoft.asc
# 添加 Microsoft 地址
vi /etc/apt/sources.list.d/microsoft-prod.list
# 添加如下一行内容
deb [arch=amd64] https://packages.microsoft.com/ubuntu/20.04/prod focal main
# 保存与退出
apt update
exit
向apt-get
源清单添加Arkane
站点
# 进入管理员模式
sudo --shell
# 安装 GPG 公钥
wget --output-document /etc/apt/trusted.gpg.d/wsl-transdebian.gpg https://arkane-systems.github.io/wsl-transdebian/apt/wsl-transdebian.gpg
chmod a+r /etc/apt/trusted.gpg.d/wsl-transdebian.gpg
# 添加 Arkane 地址
vi /etc/apt/sources.list.d/wsl-transdebian.list
# 添加如下两行内容
deb https://arkane-systems.github.io/wsl-transdebian/apt/ focal main
deb-src https://arkane-systems.github.io/wsl-transdebian/apt/ focal main
# 保存与退出
apt update
exit
编写启动脚本
创建启动脚本保存目录
在windows 10
的%USERPROFILE%
目录下,创建文件夹.ubuntu
来保存Ubuntu-20.04
的桌面系统启动脚本。这样方便从宿主环境创建快捷方式和“一站式”地启动Ubuntu-20.04(WSL)
桌面。
export USERNAME=$(wslvar USERNAME);
export USERPROFILE=/mnt/c/users/$USERNAME;
mkdir --parents $USERPROFILE/.ubuntu/
cd $USERPROFILE/.ubuntu/
编写GNOME.GTK3
启动shell
脚本
vi start_ubuntu.sh
# 添加如下内容
#!/usr/bin/env bash
# 显示环境变量,用于连接 X11 Server
if [ -z "$DISPLAY" ]; then
# 初始化 NVM 与 NODE 环境
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# 执行 nodejs 脚本程序获取宿主系统的 ip 地址。其在前文已经提过。
export DISPLAY="$(~/.get_hot_ip.js):0.0"
fi
# GNOME.GTK3 环境变量
export DESKTOP_SESSION="ubuntu"
export GDMSESSION="ubuntu"
export XDG_SESSION_DESKTOP="ubuntu"
export XDG_CURRENT_DESKTOP="ubuntu:GNOME"
export XDG_SESSION_TYPE="x11"
export XDG_BACKEND="x11"
export XDG_SESSION_CLASS="user"
export XDG_DATA_DIRS="/usr/local/share/:/usr/share/:/var/lib/snapd/desktop"
export XDG_CONFIG_DIRS="/etc/xdg"
export XDG_RUNTIME_DIR="$HOME/xdg"
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_CACHE_HOME="$HOME/.cache"
export XDG_DESKTOP_DIR="$HOME/Desktop"
export XDG_DOCUMENTS_DIR="$HOME/Documents"
export XDG_DOWNLOAD_DIR="$HOME/Downloads"
export XDG_MUSIC_DIR="$HOME/Music"
export XDG_PICTURES_DIR="$HOME/Pictures"
export XDG_PUBLICSHARE_DIR="$HOME/Public"
export XDG_TEMPLATES_DIR="$HOME/Templates"
export XDG_VIDEOS_DIR="$HOME/Videos"
# 启动 GNOME.GTK3 桌面环境
gnome-session
保存并退出
编写wsl2
启动javascript
脚本
因为有一段比较麻烦的“重试”逻辑需要被实现,所以我选择了js
--- 既简单,又强大。需实现的业务逻辑包括:
- 执行
wsl genie -c /mnt/c/Users/${process.env.USERNAME}/.ubuntu/start_ubuntu.sh
指令 - 跟踪标准输出内容,判断其是否包含字符串
Timed out waiting for systemd to enter running state.
? - 若在标准输出内包含
Timed out waiting for systemd to enter running state.
,就杀进程和回到#1
。 - 否则结束。
vi start_ubuntu.js
# 添加如下内容
#!/usr/bin/env node
const {spawn} = require('child_process');
(async () => {
const {stdout} = await exec('wsl', ['genie', '--is-in-bottle']).catch(output => output);
const shellPath = `/mnt/c/Users/${process.env.USERNAME}/.ubuntu/start_ubuntu.sh`;
if (/inside/.test(stdout)) {
await exec(shellPath);
} else {
const run = () => {
return exec('wsl', ['genie', '-c', shellPath], (_, output) => {
if (/Timed out waiting for systemd to enter running state\./.test(output.stdout)) {
output.prc.kill();
}
}).catch(err => {
if (/Timed out waiting for systemd to enter running state\./.test(err.stdout)) {
return run();
}
return Promise.reject(err);
});
}
await run();
}
})();
function exec(cmd, args = [], notify = () => {}){
return new Promise((resolve, reject) => {
const prc = spawn(cmd, args, {shell: true});
const output = {
prc,
stdout: '',
stderr: ''
};
prc.stdout.on('data', data => {
output.stdout += data.toString();
notify('stdout', output);
});
prc.stderr.on('data', data => {
output.stderr += data.toString();
notify('stderr', output);
});
prc.on('close', code => {
if (code == 0) {
resolve(output);
} else {
reject(output);
}
});
});
}
编写VcXsrv
启动配置xml
文件
vi ubuntu-desktop-wsl2-preset.xlaunch
# 添加如下内容
<?xml version="1.0" encoding="UTF-8"?>
<XLaunch WindowMode="Windowed" ClientMode="NoClient" LocalClient="False" Display="0"
LocalProgram="xcalc" RemoteProgram="xterm" RemotePassword="" PrivateKey=""
RemoteHost="" RemoteUser="" XDMCPHost="" XDMCPBroadcast="False" XDMCPIndirect="False"
Clipboard="True" ClipboardPrimary="True" ExtraParams="" Wgl="False" DisableAC="True"
XDMCPTerminate="False"/>
保存并退出
编写VcXsrv
重启powershell
脚本
vi restart_vcxsrv.ps1
# 添加如下内容
# 终止正监听于 0.0 显示码的 X11 Server 实例
get-process vcxsrv | where { $_.mainwindowtitle -like "*0.0*" } | stop-process
# 启动监听于 0.0 显示码的 X11 Server 实例
start-process "C:\Program Files\VcXsrv\xlaunch.exe" -argument "-run `"$env:USERPROFILE\.ubuntu\ubuntu-desktop-wsl2-preset.xlaunch`""
编写Ubuntu-20.04(WSL)
桌面启动引导VBS
程序
在此步骤,使用VBS
而不是javascript
。其主要原因是:由VBS
唤起的【命令行窗口】会自动隐藏起来,而由nodejs
唤起的命令行窗口就一直在那儿,不能被收起了。若强制收起,则整个进程就结束了。这虽然不影响功能,但太膈应了。
vi start_ubuntu.vbs
# 添加如下内容
set shell_object = createobject("wscript.shell")
userprofile = shell_object.ExpandEnvironmentStrings("%USERPROFILE%")
' 首先,启动 X11 Server
set application = createobject("shell.application")
application.shellexecute "powershell", "-file " & userprofile & "\.ubuntu\restart_vcxsrv.ps1", "", "", 0
' 给 X11 Server 启动,留一点时间
wscript.sleep 1000
' 再启动 wsl2
shell_object.run "node " & userprofile & "\.ubuntu\start_ubuntu.js", 0
制作Ubuntu-20.04(WSL)
图标
# 下载 Ubuntu 图标与图片集
wget https://assets.ubuntu.com/v1/9fbc8a44-circle-of-friends-web.zip
# 解压缩之
unzip 9fbc8a44-circle-of-friends-web.zip
# 重新设定 Logo 图片大小,文件类型与文件名
convert -resize 64x64 ./circle-of-friends-web/png/cof_orange_hex.png ubuntu.ico
# 删除无用文件
rm -f 9fbc8a44-circle-of-friends-web.zip
rm -fr 9fbc8a44-circle-of-friends-web
为Ubuntu-20.04(WSL)
桌面启动,创建快捷方式
在PowerShell
中,执行如下指令
# Define location variables
$shortcut_location = "$env:userprofile\.ubuntu\Ubuntu.lnk"
$program_location = "$env:userprofile\.ubuntu\start_ubuntu.vbs"
$icon_location = "$env:userprofile\.ubuntu\ubuntu.ico"
# Create shortcut
$object = new-object -comobject wscript.shell
$shortcut = $object.createshortcut($shortcut_location)
$shortcut.targetpath = $program_location
$shortcut.iconlocation = $icon_location
$shortcut.save()
便会在%USERPROFILE%\.ubuntu
目录下,看到一个Ubuntu
快捷方式。双击之就会
- 先启动
X11 Server
- 再启动
WSL2
- 接着,初始化
GNOME.GTK3
会话 - 显示
Ubuntu
桌面
收尾工作
-
双击
Ubuntu
快捷方式,启动Ubuntu
桌面 -
从
Activaties
打开Terminal
终端 -
执行如下命令
# 关闭【屏幕锁】功能。一旦被锁,非重启不能解锁。实在太恶心了。 gsettings set org.gnome.desktop.lockdown disable-lock-screen true # 安装应用商店 sudo snap install snap-store
结束
最初,我仅只想解决Rust + QT
在win32
平台上的编译问题。谁知经历了一番探索与实践却在Windows 10
平台内搞出一套完整的Linux
子系统来。这似乎又范了我做事不够专注的老毛病了。得改,得改!但是,我真心希望这里分享的内容能够帮助到技术同路人们。搞技术不容易,大家多分享,多相互帮助,共同进步。
评论区
写评论在WSL2中获取宿主IP, 可以尝试下这个命令:
cat /etc/resolv.conf | grep -oP '(?<=nameserver\ ).*'
我是在 .bashrc 中直接导出为环境变量, 使用的时候可以直接使用 $hostip 了
export hostip=$(cat /etc/resolv.conf | grep -oP '(?<=nameserver\ ).*')
另外再补充一下,我还有两个地方和你操作不一样。 首先我没有安装node.js,因为您文中并没有提供
.get_hot_ip.js
这个文件的内容,所以我用shell写了一个.get_hot_ip.sh
,在需要的地方都用这个名字了。还有我没有安装ruby依赖包,因为我没看出这两个包在这篇文章中起到了什么作用。不过我刚才把这两个gem都安装了,测试后还是相同的错误。楼主你好,非常感谢您分享的这篇文章!我照着做了几个小时,多数都跑通了,虽然遇到了一些问题,有些是我自己环境的问题,有些是您文章里没提到的问题,等等,但是最后多数都一一解决了。现在除了增补篇外就交叉编译遇到了问题无法通过,我想描述一下我的现象,希望您能帮我分析分析。
首先我看您文中说:
这里
QT_INCLUDE_PATH
的值应该是/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/include
吧?bin
是可执行文件目录,我感觉这里应该是笔误,您能确认一下吗?我下面描述的都是配置成/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/include
的情况下发生的。还有我看你文中说
所以在WSL Ubuntu中我设置的工具链是这样的:
另外我的两个环境变量也配置了:
然后切换到todos目录后尝试build,得到下面的错误信息:
我看这个错误提示找不到lib.exe,但是我查了一下我的计算机里是有这个文件的:
所以我感觉在WSL Ubuntu里编译之前还需要设置什么环境变量之类的?你有做过其他的设置吗?就是在Windows上编译,也需要执行
Developer Command Prompt for VS 2017
,其实就是设置了一些环境变量,但是我不知道在WSL Ubuntu里该怎么做。希望您能帮我指点一下,先谢谢啦!顶起 (个_个)
楼主好厉害,膜拜
一个字,膜!
WSL2 是可以改安装位置的。先导出,删除原来的系统,在导入的时候就可以指定位置了
厉害厉害,写的非常详细!
楼主好厉害!大神给跪了