< 返回我的博客

爱国的张浩予 发表于 2021-06-27 14:08

Tags:rust,qt,gui,application,cross-compilation,wsl,ubuntu

Rust+QT编程搭建【伪】win32开发环境

细心的读者一定会注意到文章标题内的【伪】字。哎!实属无奈。Rust Qt Bindingwin32编译环境实在不友善。在windows操作系统上,qmetaobject始终编译失败 --- 这是一个已备案的缺陷。幸运的是,我本地操作系统是Windows 10 x64,所以还有一条“曲线救国”的“狗血”技术路线。概括地讲,

  1. Windows 10 x64上,安装Ubuntu-20.04子系统(绝不是VM,而是WSL2)。即是传说中的Windows Subsystem for Linux 2
  2. Ubuntu-20.04子系统内,安装全套的
    1. rustup工具链
    2. Qt SDK
    3. 题外话,即便安装一个nwjs for Linux,其也是能够正常运行的。
  3. Windows 10 x64上的VSCode远程连接至Ubuntu-20.04。这样一来,
    1. 既将VSCode人机交互界面继续保留在Windows端 --- 开发体验不降级。
    2. 又将rustc编译与Qt link都迁移至Linux端执行 --- 绕开qmetaobjectwindows编译环境的不兼容。
  4. Ubuntu-20.04的图形界面投影至Windows 10 x64。以便,能够在windows 10环境上,观察GUI程序的修改效果 --- 这还是为保持开发体验不降级。
  5. 在功能开发结束之后,在target/releasetarget/debug目录下的就是Linux版的GUI应用程序分发包。
  6. 需要借助【交叉编译】,才能在target/x86_64-pc-windows-msvc文件夹内,获得Windows版的GUI应用程序分发包。

下面就是这一出既“狗血”又超级麻烦的自虐之旅的详细内容。希望世界变得更平,别让我这样的Windows狗活得那么辛苦。

为什么选择QT?

前不久,我写的另一篇文章Rust原生gui编程,搭建win32开发环境分享了如何将Rust链接Gnome.GTK3实现GUI应用程序开发。对照两款图形界面解决方案,Gnome.GTK3上手容易,文档齐备,社区活跃。但是,QT强烈吸引我的有两点:

  1. 覆盖全平台。
    1. 从【桌面】,到【移动端】,再到【嵌入式设备】
    2. Windows,到LinuxMac,再到AndroidiOS
      1. 虽然眼下Rust Qt Bindingwindows操作系统的兼容性还有不足,但我相信这仅只是临时缺陷,会被尽快修复。
  2. 可移植性强。
    1. 至少它依赖的动态链接库里没有与win32内置dll重名的。Gnome.GTK3的这个毛病可是把我给恶心到了。我之前可是下了佬大的时间与精力才定位出此crash的原因。

我甚至有一个不成熟的想法:“QT才是前端开发者最终的技术归宿”,因为它:

  1. 全平台
  2. 高性能
  3. 亲和IoT --- 还是有相当多的IoT设备是拥有独立屏显的。不可能在所有的IoT硬件上都运行一个web service等着别人用浏览器访问(这不是高端玩家的作法)。

不过在这里,我还是想分别对Gnome.GTK3QT分别吐槽三点:

  1. Gnome.GTK3对·新人上手·真是没得说、太棒了。但是,其【高级组件】(比如,webview)不支持windows平台太挫伤开发者的深度使用热情了。
  2. QT几乎无所不能。但是,Rust Qt Bindingwin32编译环境的适用性太差。这对普通玩家的入手门槛有些高了。
  3. 最后,上面两个问题都不是新问题,而都是陈年老梗了。官方怎么对这些缺陷的解决这么不上心呀?

为什么选择qmetaobject

  1. qmetaobjectQT官方团队维护的项目。我相信其后续迭代有保障。QT官宣软文看这里
  2. 你不会以为其它的Rust Qt Binding解决方案对win32编译环境友善吧?Naively! 事实上,以下几款Rust Qt Binding开源项目,我都试过了。它们中没一个对win32友好的。此时,我脸上只有一个表情就是“悲愤”。世界一点儿也不平!
    1. ritual
    2. qmlrs
    3. qml-rust

劝退理由

整个开发环境包括子系统Ubuntu-20.04 (WSL)和宿主Windows 10 x64两个部分。其对开发环境的软件与硬件条件都有一点儿要求:

  1. 低版本的Windows操作系统不具备WSL2功能。所以,如果你的操作系统是Windows 7,推荐先研究一下如何免费地升级到Windows 10
  2. 从微软应用程序市场下载安装的Ubuntu-20.04 (WSL2)仅只是一个Linux Kernel(大约1.2GB)。所以,后续需要安装的软件包非常地多。你的系统盘足够大吗?若系统盘的存储余额小于25GB,推荐先研究一下【系统盘】“搬家”以腾出足够的空间折腾。

开发环境搭建概述

被用来验证开发环境的Rust + QT工程是来自官方的演示例程todos

整个安装配置大约是以下若干步:

  1. 安装与配置Ubuntu-20.04 (WSL2)
  2. 配置宿主端Windows 10
  3. 回到Ubuntu-20.04 (WSL2)安装Qt SDK。因为QT的连线安装程序是GUI的,所以必须先在第二步解锁【从WSL2子系统向宿主系统投影图形界面】的技能。
  4. Ubuntu-20.04端,使用VSCode打开todos工程。
  5. VSCode集成终端,执行cargo run弹出应用程序窗口,验证开发环境正常工作。

开发环境搭建之子系统端 - Ubuntu-20.04 (WSL2)

安装Ubuntu 20.04 (WSL2)子系统

我给WSL2子系统安装Ubuntu不是因为Ubuntu有什么独一无二的技术优势;而仅是,相对于CentOSUbuntu 20.04子系统不要钱。在微软应用程序商店,除了Mac以外(知识产权保护)的桌面系统都有WSL2镜像安装包。它们之间最大的差别就是【收费】与【免费】。

再次劝退

Windows Subsystem for Linux 2会被强制安装于系统C。所以,在安装Ubuntu 20.04 (WSL2)子系统之前,请先确认你的系统盘是否还有足够的剩余空间。在我的使用场景里,子系统满载体量大约14GB(以后,可能还会更大)。虽然WSL2既好看又好用,但它是有成本的。

Ubuntu 20.04 (WSL2)子系统与Windows 10 x64宿主系统之间的关系

  1. 硬盘关系
    1. 子系统的硬盘对宿主系统是不可见。
    2. 宿主系统的硬盘是被逐个mountedUbuntu 20.04 (WSL2)【挂载点】/mnt虚拟文件夹下,所以宿主系统文件系统对子系统皆是可见的,且被视作外部存储设备
  2. 环境变量关系
    1. 子系统会继承(甚至,重写)宿主系统的全部环境变量 --- 这个潜在地造成了混乱,我后面会给出规避方式。
    2. 宿主系统访问不到子系统内的环境变量。
  3. 程序执行关系
    1. 子系统可运行宿主系统的.exe文件。比如,ipconfig.exe --- 后面会用到。
    2. 宿主系统既看不到,也执行不了子系统的可执行文件。

安装步骤

  1. 以【管理员】身份运行PowerShell

  2. 激活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
    
  3. 下载与安装Linux Kernel 更新包

  4. WSL2设置成为默认启动项

    wsl --set-default-version 2
    
  5. 从【微软应用程序商店】搜索Ubuntu 20.04 LTS并安装之。除了Ubuntu外,还提供有CentOS子系统。但,这是收费的。

WSL2子系统基本操作

  1. 开机

    wsl -d Ubuntu-20.04
    

    在第一次启动Ubuntu 20.04 LTS子系统时,你会被要求创建一个管理员账号。根据提示,你照做就是了。

  2. 关机

    wsl --shutdown
    # 或
    wsl -t Ubuntu-20.04
    
  3. 打开运行中子系统的终端

    wsl
    
  4. 列出所有已安装的子系统

    wsl -l -v
    

上面所有操作在cmdPowerShell均可完成。但,我还是推荐安装windows terminal

配置Ubuntu 20.04 (WSL2)子系统

启动Ubuntu 20.04 (WSL2)子系统

打开cmdPowerShell终端,执行

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仅只对目录有效,一次至少会导入一个文件夹的动态链接库,简直是后患无穷。在本次开发环境搭建过程中,涉及到的符号链接只有两个:

  1. ipconfig.exe - 下文会提到使用它来实时地获取宿主操作系统的ip地址。
  2. VSCode的启动脚本文件${VSCode_安装目录}/bin/code - 以便从子系统端打开一个工程和编辑代码。

安装node.js

先安装nvm版本管理器,再安装nodenpmpnpm

提示:安装脚本可能需要“科学上网”才能被下载。所以,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还是20213月的版本。借助我所做的另一款开源工具rustup-components-history,你能找到更多的nightly rustup toolchain的历史版本。毕竟,官方给出的历史版本可用性清单仅提供最新7天的信息。

大家看,我的相关功课做得全吧?是不是,无处不惊喜?所以,请帮忙,多转发,多参阅。

配置XSTATA向宿主系统投影图形界面

题外话,XSTATAX11 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配置文件往往是各种坏事情的开端。

为了缓解这个痛点,我采取的措施包括两步:

  1. /snap/bin目录下,给C:\Windows\System32\ipconfig.exe创建一个符号连接/snap/bin/ipconfig.exe

    sudo ln -s /mnt/c/Windows/System32/ipconfig.exe /snap/bin/ipconfig.exe
    
  2. $HOME目录下,编写了一个.get_hot_ip.js脚本程序,来

    1. 执行/snap/bin/ipconfig.exe命令
    2. 解析/snap/bin/ipconfig.exe在标准输出打印的文本内容
    3. 从输出内容里扣出宿主系统的ip地址 --- 需要一点儿简单的正则匹配
    4. 打印此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

  1. 下载与安装VcXsrv
  2. 设置【高DPI显示模式】
    1. 打开VcXsrv安装目录
    2. 鼠标右键点击xlaunch.exe文件。
    3. 点击【属性】菜单项
    4. 在【属性】对话框内,切签至【兼容性】选项卡
    5. 点击【更高DPI设置】按钮
    6. 在新对话框内,选中【替代高DPI缩放行为】复选框。
    7. 在【缩放执行】下拉框内选择【系统(增强)】
    8. 最后,一路【确定】下来。

启动X-Server for WinNT

生成启动配置文件

  1. 双击VcXsrv安装目录(下文记作:%VCXSRV_HOME%)内的XLaunch.exe文件
  2. 打开Display Settings配置窗口
    1. 选择Multiple Windows(就是左一项)
    2. Display number数字录入框内,输入0
    3. 点击【下一页】按钮
  3. 跳转至Client startup配置窗口
    1. 选择Start no Client
    2. 点击【下一页】按钮
  4. 跳转至Extra Settings配置窗口
    1. 选中Clipboard
      1. 选中Primary Selection
    2. 取消选中Native opengl
    3. 选中Disable access control
    4. 点击【下一页】按钮
  5. 跳转至Finish configuration配置窗口
    1. 点击按钮save configuration,保存配置为文件%VCXSRV_HOME%\x11-server.xlaunch
    2. 点击【取消】按钮。

将配置文件应用于快捷方式

  1. 鼠标右(键)击XLaunch快捷方式

  2. 在【属性】对话框中,修改【目标】输入框的内容为

    "%VCXSRV_HOME%\xlaunch.exe" -run "%VCXSRV_HOME%\x11-server.xlaunch"
    
  3. 点击【确定】按钮

启动X11 Server

双击新XLaunch快捷方式

配置VSCode

  1. 安装插件
    1. Remote - WSL
    2. Remote Development
  2. 在【用户配置】文件%AppData%\Code\User\settings.json里,添加一个新配置项"http.proxySupport": "off"。否则,在WSL2模式下,安装扩展插件容易遭遇下载网络失败。
  3. 关闭当前VSCode实例
  4. Ubuntu-20.04(WSL)终端,敲入指令code .。在WSL2模式下,启动另一个VSCode实例。
  5. 给运行于WSL2模式下的VSCode实例安装三个rust插件
    1. rust-analyzer
    2. vscode-rust-syntax
    3. CodeLLDB

Ubuntu-20.04 (WSL2)子系统安装Qt-5.12.11

准备工作

  1. Windows 10 x64宿主系统,双击XLaunch快捷方式启动X11 Server服务,准备接受来自WSL2子系统的图形界面投影。
  2. Ubuntu-20.04终端命令行,执行source ~/.bashrc确保之前的配置修改皆奏效。

安装框架与组件库

  1. 打开Ubuntu-20.04 (WSL2)命令行终端

    wsl
    
  2. 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
    
  3. 于是,在Windows 10 x64宿主端,Qt安装向导对话框就会被弹出。此时,若完全安装Qt-5.12.11框架和组件库,则需要几十个GB的空间。我推荐:

    1. 只安装Desktop gcc 64-bitQt WebEnginewebview这是刚需)就足够了。
    2. 选择安装目录为$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)开发环境

  1. 检查桌面右下角的托盘,确认X11 Server是否已经启动。
  2. 克隆工程从github至本地git clone https://github.com/woboq/qmetaobject-rs.git
    1. 这一步发生在宿主系统或Ubuntu-20.04 (WSL2)子系统不重要。
  3. WSL2命令行终端,cd工程根目录/examples/todos
  4. 输入指令code .,回车。以WSL2模式,启动一个VSCode实例。
  5. 打开VSCode集成终端,敲入cargo run命令,开始编译/运行程序。
    1. 另一方面,若你本地已经配置好了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工具链。在这,你是不是也感觉心拔凉拔凉的?

然后,咱们再讲怎么搞【交叉编译】。

  1. 在上文中的【rustup toolchain安装】章节,咱们已经预安装了x86_64-pc-windows-msvc``target

    1. target可不认识nightly rust语法,会编译报错的。
  2. 除此之外,我们还要准备一套Qt for Windows

    1. 直接在你的宿主端windows 10 x64上,下载与运行Qt for Windows 连线安装向导
    2. 勾选安装Qt 5.12.11 -> MSVC 2017 64-bit注意:我勾选安装MSVC 2017 64-bit是因为我本地已经有Visual Studio 2017了。 请先确定你自己电脑的预装了哪一版的Visual Studio再合理地选择Qt MSVC类型。
  3. 接着,仅交叉编译时,临时配置环境变量QT_INCLUDE_PATHQT_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_PATHQT_LIBRARY_PATH优先级高于PATHLD_LIBRARY_PATH,所以后续的【交叉编译】会优先链接QT for Windows,而不是开发时依赖的QT for Linux

  4. 最后,cd工程根目录/examples/todos和执行命令cargo build --target=x86_64-pc-windows-msvc。完成。

    1. 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给改坏了,也不用着急。

  1. 首先,从win32命令行执行wsl -u root以超级用户登录Ubuntu-20.04(WSL)
  2. 然后,强制编辑/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 --- 既简单,又强大。需实现的业务逻辑包括:

  1. 执行wsl genie -c /mnt/c/Users/${process.env.USERNAME}/.ubuntu/start_ubuntu.sh指令
  2. 跟踪标准输出内容,判断其是否包含字符串Timed out waiting for systemd to enter running state.
  3. 若在标准输出内包含Timed out waiting for systemd to enter running state.,就杀进程和回到#1
  4. 否则结束。
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快捷方式。双击之就会

  1. 先启动X11 Server
  2. 再启动WSL2
  3. 接着,初始化GNOME.GTK3会话
  4. 显示Ubuntu桌面

收尾工作

  1. 双击Ubuntu快捷方式,启动Ubuntu桌面

  2. Activaties打开Terminal终端

    ubuntu-desktop-snapshot

  3. 执行如下命令

    # 关闭【屏幕锁】功能。一旦被锁,非重启不能解锁。实在太恶心了。
    gsettings set org.gnome.desktop.lockdown disable-lock-screen true
    # 安装应用商店
    sudo snap install snap-store
    

结束

最初,我仅只想解决Rust + QTwin32平台上的编译问题。谁知经历了一番探索与实践却在Windows 10平台内搞出一套完整的Linux子系统来。这似乎又范了我做事不够专注的老毛病了。得改,得改!但是,我真心希望这里分享的内容能够帮助到技术同路人们。搞技术不容易,大家多分享,多相互帮助,共同进步。

评论区

写评论
huanfeng 2021-09-28 11:00

在WSL2中获取宿主IP, 可以尝试下这个命令:

cat /etc/resolv.conf | grep -oP '(?<=nameserver\ ).*'

我是在 .bashrc 中直接导出为环境变量, 使用的时候可以直接使用 $hostip 了

export hostip=$(cat /etc/resolv.conf | grep -oP '(?<=nameserver\ ).*')

lajiwangpan1 2021-08-13 18:30

另外再补充一下,我还有两个地方和你操作不一样。 首先我没有安装node.js,因为您文中并没有提供.get_hot_ip.js这个文件的内容,所以我用shell写了一个.get_hot_ip.sh,在需要的地方都用这个名字了。还有我没有安装ruby依赖包,因为我没看出这两个包在这篇文章中起到了什么作用。不过我刚才把这两个gem都安装了,测试后还是相同的错误。

lajiwangpan1 2021-08-13 18:20

楼主你好,非常感谢您分享的这篇文章!我照着做了几个小时,多数都跑通了,虽然遇到了一些问题,有些是我自己环境的问题,有些是您文章里没提到的问题,等等,但是最后多数都一一解决了。现在除了增补篇外就交叉编译遇到了问题无法通过,我想描述一下我的现象,希望您能帮我分析分析。

首先我看您文中说:

接着,仅交叉编译时,临时配置环境变量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的值应该是/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/include吧?bin是可执行文件目录,我感觉这里应该是笔误,您能确认一下吗?我下面描述的都是配置成/mnt/${QT_WINDOWS_安装目录}/5.12.11/msvc2017_64/include的情况下发生的。

还有我看你文中说

首先,要明确【rust交叉编译】是有限制的。即,rustup工具链的nightly频道对【交叉编译】都不可用。我们仅能使用stable rustup工具链。

所以在WSL Ubuntu中我设置的工具链是这样的:

user1@DESKTOP-B8VT41G:~/qmetaobject-rs/examples/todos$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/user1/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu (default)
nightly-2021-03-25-x86_64-unknown-linux-gnu

installed targets for active toolchain
--------------------------------------

x86_64-pc-windows-msvc
x86_64-unknown-linux-gnu

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.54.0 (a178d0322 2021-07-26)

user1@DESKTOP-B8VT41G:~/qmetaobject-rs/examples/todos$

另外我的两个环境变量也配置了:

QT_INCLUDE_PATH: /mnt/c/Qt/5.12.11/msvc2017_64/include
QT_LIBRARY_PATH: /mnt/c/Qt/5.12.11/msvc2017_64/lib

然后切换到todos目录后尝试build,得到下面的错误信息:

user1@DESKTOP-B8VT41G:~/qmetaobject-rs/examples/todos$ cargo build --target=x86_64-pc-windows-msvc
   Compiling qttypes v0.2.3 (/home/user1/qmetaobject-rs/qttypes)
The following warnings were emitted during compilation:

warning: In file included from /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qabstractitemmodel.h:43,
warning:                  from /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/QModelIndex:1,
warning:                  from src/lib.rs:165:
warning: /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qvariant.h: In constructor ‘QVariant::QVariant(QVariant&&)’:
warning: /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qvariant.h:273:25: warning: implicitly-declared ‘QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]
warning:   273 |     { other.d = Private(); }
warning:       |                         ^
warning: /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qvariant.h:399:16: note: because ‘QVariant::Private’ has user-provided ‘QVariant::Private::Private(const QVariant::Private&)’
warning:   399 |         inline Private(const Private &other) Q_DECL_NOTHROW
warning:       |                ^~~~~~~

error: failed to run custom build command for `qttypes v0.2.3 (/home/user1/qmetaobject-rs/qttypes)`

Caused by:
  process didn't exit successfully: `/home/user1/qmetaobject-rs/target/debug/build/qttypes-f609b81e4df6363b/build-script-build` (exit status: 1)
  --- stdout
  cargo:rerun-if-env-changed=QT_INCLUDE_PATH
  cargo:rerun-if-env-changed=QT_LIBRARY_PATH
  TARGET = Some("x86_64-pc-windows-msvc")
  OPT_LEVEL = Some("0")
  HOST = Some("x86_64-unknown-linux-gnu")
  CXX_x86_64-pc-windows-msvc = None
  CXX_x86_64_pc_windows_msvc = None
  TARGET_CXX = None
  CXX = None
  CROSS_COMPILE = None
  CXXFLAGS_x86_64-pc-windows-msvc = None
  CXXFLAGS_x86_64_pc_windows_msvc = None
  TARGET_CXXFLAGS = None
  CXXFLAGS = None
  CRATE_CC_NO_DEFAULTS = None
  DEBUG = Some("true")
  CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
  CXX_x86_64-pc-windows-msvc = None
  CXX_x86_64_pc_windows_msvc = None
  TARGET_CXX = None
  CXX = None
  CROSS_COMPILE = None
  CXXFLAGS_x86_64-pc-windows-msvc = None
  CXXFLAGS_x86_64_pc_windows_msvc = None
  TARGET_CXXFLAGS = None
  CXXFLAGS = None
  CRATE_CC_NO_DEFAULTS = None
  CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
  running: "c++" "-O0" "-ffunction-sections" "-fdata-sections" "-g" "-fno-omit-frame-pointer" "-m64" "-I" "/home/user1/qmetaobject-rs/qttypes" "-I" "/mnt/c/Qt/5.12.11/msvc2017_64/include" "-Wall" "-Wextra" "-std=c++11" "-Fo/home/user1/qmetaobject-rs/target/x86_64-pc-windows-msvc/debug/build/qttypes-dd6b6c0d8d0f46ad/out/rust_cpp/cpp_closures.o" "-c" "/home/user1/qmetaobject-rs/target/x86_64-pc-windows-msvc/debug/build/qttypes-dd6b6c0d8d0f46ad/out/rust_cpp/cpp_closures.cpp"
  cargo:warning=In file included from /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qabstractitemmodel.h:43,
  cargo:warning=                 from /mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/QModelIndex:1,
  cargo:warning=                 from src/lib.rs:165:
  cargo:warning=/mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qvariant.h: In constructor ‘QVariant::QVariant(QVariant&&)’:
  cargo:warning=/mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qvariant.h:273:25: warning: implicitly-declared ‘QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]
  cargo:warning=  273 |     { other.d = Private(); }
  cargo:warning=      |                         ^
  cargo:warning=/mnt/c/Qt/5.12.11/msvc2017_64/include/QtCore/qvariant.h:399:16: note: because ‘QVariant::Private’ has user-provided ‘QVariant::Private::Private(const QVariant::Private&)’
  cargo:warning=  399 |         inline Private(const Private &other) Q_DECL_NOTHROW
  cargo:warning=      |                ^~~~~~~
  exit status: 0
  AR_x86_64-pc-windows-msvc = None
  AR_x86_64_pc_windows_msvc = None
  TARGET_AR = None
  AR = None
  running: "lib.exe" "-out:/home/user1/qmetaobject-rs/target/x86_64-pc-windows-msvc/debug/build/qttypes-dd6b6c0d8d0f46ad/out/librust_cpp_generated.a" "-nologo" "/home/user1/qmetaobject-rs/target/x86_64-pc-windows-msvc/debug/build/qttypes-dd6b6c0d8d0f46ad/out/rust_cpp/cpp_closures.o"

  --- stderr

  error occurred: ToolNotFound: Failed to find tool. Is `lib.exe` installed?

user1@DESKTOP-B8VT41G:~/qmetaobject-rs/examples/todos$

我看这个错误提示找不到lib.exe,但是我查了一下我的计算机里是有这个文件的:

C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\lib.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x86\lib.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\bin\Hostx86\x64\lib.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\bin\Hostx86\x86\lib.exe

所以我感觉在WSL Ubuntu里编译之前还需要设置什么环境变量之类的?你有做过其他的设置吗?就是在Windows上编译,也需要执行Developer Command Prompt for VS 2017,其实就是设置了一些环境变量,但是我不知道在WSL Ubuntu里该怎么做。希望您能帮我指点一下,先谢谢啦!

Freddie Mercury 2021-07-09 13:28

顶起 (个_个)

timosong1314 2021-06-29 12:56

楼主好厉害,膜拜

Mike Tang 2021-06-28 22:21

一个字,膜!

bzeuy 2021-06-28 18:12

WSL2 是可以改安装位置的。先导出,删除原来的系统,在导入的时候就可以指定位置了

ZK645968 2021-06-28 17:05

厉害厉害,写的非常详细!

wajjforever1314 2021-06-28 17:03

楼主好厉害!大神给跪了

1 共 9 条评论, 1 页