Windows で RaspberryPi 向け Qt6 クロス開発 (その1：準備編)

今回故あって Raspberry Pi で動く Qt のプログラム開発を Windows でやりたいという話がありました。  

これを実現するためには「クロス開発環境」が必要です。  
もちろん Qt 自体もそうできるよう自分で構築する必要があります。

今回はその準備編です。

# 元にした情報
* [Cross-Compile Qt 6 for Raspberry Pi](https://wiki.qt.io/Cross-Compile_Qt_6_for_Raspberry_Pi)
  * Qt 公式の Raspberry Pi 4 向けクロス環境構築情報
  * ホストが Linux な情報ですがこれを Windows でやろう、というのが今回の記事です

# ターゲット
Raspberry Pi で QtQuick3D を動かしてみたいのですが、  
デスクトップは不要なので OS も GUI なしの環境をターゲットにして Qt の EGLFS という直接描画を使います。  
X Window 向けのものを作る場合については補足記事で紹介します。

* Qt 6.5.1
* ホスト側
  * Windows 11 Home
* ターゲット側
  * Raspberry Pi 3 Model B (以下 RPi と表記)
  * Raspberry Pi OS Lite bullseye 64bit (以下 RPiOS と表記)
    * 公式イメージの最新のもので OK
    * 64bit 版で進めます
```bash
$ cat /proc/version 
Linux version 6.1.21-v8+ (dom@buildbot) (aarch64-linux-gnu-gcc-8 (Ubuntu/Linaro 8.4.0-3ubuntu1) 8.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023
$ cat /etc/debian_version 
11.7
```

# クロス開発環境構築の手順

1. RaspberryPi 側の準備
   1. OS イメージ作成と起動
   2. Qt の開発に必要なパッケージの導入
   3. 環境設定
2. Windows 側の準備
   1. Qt 開発環境の導入
   2. クロスコンパイラを導入
   3. sysroot のコピー
   4. CMake と Ninja-build を実行できるようにする
   5. pkg-config の導入と設定
   6. CMake 用の設定ファイル作成
3. Qt のビルド (→ [【ビルド＆実行編】](https://qiita.com/tkhshmsy/items/604ccf95be93ecc674c2))
   1. ソースコード取得
   2. configure
   3. ビルドとインストール
   4. RPi へのコピー
4. QtCreator の設定
   1. リモートデバイスの設定
   2. Qt キットの設定
   3. サンプルの動作確認
5. 補足 (→ [【補足編】](https://qiita.com/tkhshmsy/items/579781fde85817174a4c))
   1. クロス開発環境を別の WinPC へ持ち込みたい
   2. RPi側実行環境の OS イメージを小さくしたい
   3. X Window アプリを作れるようにしたい
   4. 32bit のクロス環境を作りたい
   5. まだできていないこと

# RaspberryPi 側の準備

## OS イメージ作成と起動
[Raspberrypi.com](https://www.raspberrypi.com/) 公式の手順に沿って SD カードを作成します。  

SD カードの書き込みには [rpi-imager](https://www.raspberrypi.com/software/) を使うと初期設定もできて便利です。  
以下のように設定したものとして進めます
* username: pi
* hostname: raspberrypi
* ssh 有効

書き込んだ SD カードを RPi に挿入して起動しましょう

## Qt の開発に必要なパッケージの導入
RPi の起動後、ssh でログインします。  
```bash
## アップデート
$ sudo apt update && sudo apt upgrade -y

## Qt6 に必要な開発パッケージの導入
$ sudo apt-get install libboost-all-dev libudev-dev libinput-dev libts-dev libmtdev-dev \ 
libjpeg-dev libfontconfig1-dev libssl-dev libdbus-1-dev libglib2.0-dev libxkbcommon-dev \ 
libegl1-mesa-dev libgbm-dev libgles2-mesa-dev mesa-common-dev libasound2-dev \ 
libpulse-dev gstreamer1.0-omx libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ 
gstreamer1.0-alsa libvpx-dev libsrtp2-dev libsnappy-dev libnss3-dev "^libxcb.*" \ 
flex bison libxslt-dev ruby gperf libbz2-dev libcups2-dev libatkmm-1.6-dev libxi6 \ 
libxcomposite1 libfreetype6-dev libicu-dev libsqlite3-dev libxslt1-dev libavcodec-dev \ 
libavformat-dev libswscale-dev libx11-dev freetds-dev libsqlite3-dev libpq-dev \ 
libiodbc2-dev firebird-dev libgst-dev libxext-dev libxcb1 libxcb1-dev libx11-xcb1 \ 
libx11-xcb-dev libxcb-keysyms1 libxcb-keysyms1-dev libxcb-image0 libxcb-image0-dev \ 
libxcb-shm0 libxcb-shm0-dev libxcb-icccm4 libxcb-icccm4-dev libxcb-sync1 libxcb-sync-dev \ 
libxcb-render-util0 libxcb-render-util0-dev libxcb-xfixes0-dev libxrender-dev \ 
libxcb-shape0-dev libxcb-randr0-dev libxcb-glx0-dev libxi-dev libdrm-dev \ 
libxcb-xinerama0 libxcb-xinerama0-dev libatspi2.0-dev libxcursor-dev libxcomposite-dev \ 
libxdamage-dev libxss-dev libxtst-dev libpci-dev libcap-dev libxrandr-dev \ 
libdirectfb-dev libaudio-dev libxkbcommon-x11-dev libpcre2-dev
```

## 環境設定
続けて作業します
```bash 
## Qt のインストール先の作成
$ sudo mkdir -p /opt/qt
$ sudo chmod a+rw /opt/qt

## 環境変数の追加(エディタで書き足してもOK)
$ echo "export LD_LIBRARY_PATH=/opt/qt/lib" >> ${HOME}/.profile
$ echo "export QT_QPA_PLATFORM=eglfs" >> ${HOME}/.profile

## OpenGLドライバを有効化(再起動されます)
### Advanced options → GL Driver → GL (Full KMS)
$ sudo raspi-config
```

# Windows 側の準備
## Qt 開発環境の導入
Windows に Qt6 を導入します。

https://www.qt.io/download

https://www.qt.io/ja-jp/download-open-source

インストーラから、以下を追加選択して導入しておきます。  
インストール先はデフォルトの C:\Qt 以下ということで進めます。

* Qt/Qt 6.5.1
  * MinGW 11.2.0 64bit
  * Sources
* Developer and Designer Tools
  * QtCreator
  * MinGW 11.2.0 64-bit
  * CMake 3.24.2
  * Ninja 1.10.2

細かいバージョンは気にしないで大丈夫です。  
また、選択しそこねてもメンテナンスツール (C:\Qt\MaintenanceTool.exe) で追加できます。

## クロスコンパイラの導入
Windows で動作する IntelCPU の環境(x86_64)から ARM 向けバイナリ (aarch64) を出力する C/CXX クロスコンパイラが必要です。

Docker や WSL 上に導入して使うのが一般的ですが、Windows ネイティブに動くものがあると簡単便利です。  
ありがたいことにいくつか公開されています。
* SYSPROGS の gnutoolchains SysGCC
* Linaro の GNU Toolchain Integration

など、今回は SYSPROGS の [Prebuilt Windows Toolchain for Raspberry Pi (64-bit)](https://gnutoolchains.com/raspberry64/) を使用します。  
今回はデフォルト通り、C:\SysGCC\raspberry64\ にインストールします。  
![SysGCC-install.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432395/f543deec-5658-97e0-d25b-4aff10ceab18.jpeg)

## sysroot のコピー
RPiOS の SD カードから sysroot をコピーします。  
コピー先は `C:\SysGCC\raspberry64\aarch64-linux-gnu\sysroot` 以下です。

1. コピー先に元からある `/usr` と `/lib` は削除します。 
2. RPiOS のディレクトリ 2つ をコピーします。
   * `/usr/lib/`
   * `/usr/include/`
3. 次にコピーした `/usr/lib` の内容を `/lib` としてもコピーします。
   * (RPi 上でシンボリックリンクとなっているための対応です[^1])
4. 最後に RPi の `/usr/share/pkgconfig` の中身全てをコピー先の `/usr/lib/aarch64-linux-gnu/pkgconfig` へコピーします。
   * (後述する pkg-config の windows 特有の対応です)

[^1]: mklinkコマンドによるシンボリックリンク作成では対応不可でした

:::note info  
コピーにはコマンドプロンプトの scp や WinSCP を使って OK です。  
その際にコピーできないファイルがあった場合、そのファイルはスキップして問題ありません。
:::

![sysroot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432395/811c9f41-63fa-6e88-dbed-dfc61ebf5d69.jpeg)


## MinGW64 環境の整備
ビルド作業はコマンドラインを使用していきます。  
Qt のインストール内容に MinGW64 環境が含まれています。
スタートメニュー → すべてのアプリ → Qt → Qt6.5.1/MinGW 11.2.0 64-bit で起動できます。

このコマンドラインで使うツールを整備していきます。

### Python 3.x の導入
Microsoft ストアから Python 3.x を導入します。  
すでに他の Python 3 系があって動作するならならそれでも構いません。

```bash
C:\Qt\6.5.1\mingw_64> python --version
Python 3.11.4
```

### git for windows の導入
ソースコードの取得に git を使うので最新のものを導入します。  

https://gitforwindows.org/

```bash
C:\Qt\6.5.1\mingw_64> git --version
git version 2.41.0.windows.1
```

### CMake と Ninja-build を実行できるようにする
Qt の環境には CMake と ninja-build が含まれているのですがパスが通っていないため起動できません。

スタートメニュー → 設定 → システム → バージョン情報 → システムの詳細設定 でシステムのプロパティを開き、  
環境変数ボタンを押して、システム環境変数の path へ以下を追加します。

* C:\Qt\Tools\Ninja
* C:\Qt\Tools\CMake_64\bin
* C:\Qt\Tools\mingw1120_64\bin

```bash
C:\Qt\6.5.1\mingw_64> cmake --version
cmake version 3.24.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).

C:\Qt\6.5.1\mingw_64> ninja --version
1.10.2
```

### pkg-config の導入
:::note info  
pkg-config を導入しなくても Qt6 のビルド＆導入は可能です。  
ここでは Qt 以外のライブラリも使う場合にあると便利ですので導入しておきます。
:::

以下からそれぞれ DL 後、展開、ファイルを取り出しコピーします。
* https://download.gnome.org/binaries/win32/dependencies/ から、
  * `pkg-config_0.26-1_win32.zip` をDL・展開して、`bin/` の中から `pkg-config.exe`  を取得
  * `gettext-runtime_0.18.1.1-2_win32.zip` をDL・展開して、`bin/` の中から `intl.dll`  を取得
* https://download.gnome.org/binaries/win32/glib/2.28/ から、
  * `glib_2.28.8-1_win32.zip` をDL・展開して、`bin/` の中から `libglib-2.0-0.dll` を取得

コピー先は `C:\Qt\Tools\ming1120_64\bin` です。

システムの環境変数に以下の2つを追加します。  
Windows の環境変数ですがパスの区切りは「/」(半角スラッシュ)で書きます。
* PKG_CONFIG_LIBDIR
  * `C:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig`
* PKG_CONFIG_SYSROOT_DIR
  * `C:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot`

![windows-env.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432395/f5efc022-bad1-bf9b-ecea-56ac07c80786.png)

pkg-config の実行結果がすべて sysroot 以下のフォルダからヘッダファイルやライブラリを探すようになっていれば OK です。

```bash
## 動作確認
C:\Qt\6.5.1\mingw_64> pkg-config --version
0.26

## 環境変数
C:\Qt\6.5.1\mingw_64> set PKG
PKG_CONFIG_LIBDIR=C:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
PKG_CONFIG_SYSROOT_DIR=C:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot

## 試しに atspi-2 ライブラリの情報をとってみる
C:\Qt\6.5.1\mingw_64> pkg-config --cflags --libs atspi-2
-IC:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/include/at-spi-2.0
-IC:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/include/dbus-1.0
-IC:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu/dbus-1.0/include
-IC:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/include/glib-2.0
-IC:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu/glib-2.0/include
-LC:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot/usr/lib/aarch64-linux-gnu
-latspi -ldbus-1 -lglib-2.0
```

## CMake 用の設定ファイル作成

ここまでの情報をまとめた設定ファイルを作成します。
`C:\Qt\qt6build\toolchain.cmake` の名前で保存します。

```CMake
cmake_minimum_required(VERSION 3.18)    
include_guard(GLOBAL)

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_GENERATOR "Ninja" CACHE INTERNAL "" FORCE)
set(TARGET_SYSROOT C:/SysGCC/raspberry64/aarch64-linux-gnu/sysroot)
set(CMAKE_SYSROOT ${TARGET_SYSROOT})

set(PKG_CONFIG_PATH ${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig:${TARGET_SYSROOT}/usr/share/pkgconfig)
set(PKG_CONFIG_LIBDIR ${TARGET_SYSROOT}/usr/lib/pkgconfig:${TARGET_SYSROOT}/usr/share/pkgconfig:${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig:${TARGET_SYSROOT}/usr/lib/pkgconfig)
set(PKG_CONFIG_SYSROOT_DIR ${CMAKE_SYSROOT})

# if you use other version of gcc and g++ than gcc/g++ 9, you must change the following variables
set(CMAKE_C_COMPILER C:/SysGCC/raspberry64/bin/aarch64-linux-gnu-gcc.exe)
set(CMAKE_CXX_COMPILER C:/SysGCC/raspberry64/bin/aarch64-linux-gnu-g++.exe)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${TARGET_SYSROOT}/usr/include")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

set(QT_COMPILER_FLAGS "-march=armv8-a")
set(QT_COMPILER_FLAGS_RELEASE "-O2 -pipe")
set(QT_LINKER_FLAGS "-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -Wl,-rpath-link,${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu")

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
set(CMAKE_BUILD_RPATH ${TARGET_SYSROOT}:${TARGET_SYSROOT}/usr/lib:${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu)

include(CMakeInitializeConfigs)

function(cmake_initialize_per_config_variable _PREFIX _DOCSTRING)
  if (_PREFIX MATCHES "CMAKE_(C|CXX|ASM)_FLAGS")
    set(CMAKE_${CMAKE_MATCH_1}_FLAGS_INIT "${QT_COMPILER_FLAGS}")
        
    foreach (config DEBUG RELEASE MINSIZEREL RELWITHDEBINFO)
      if (DEFINED QT_COMPILER_FLAGS_${config})
        set(CMAKE_${CMAKE_MATCH_1}_FLAGS_${config}_INIT "${QT_COMPILER_FLAGS_${config}}")
      endif()
    endforeach()
  endif()


  if (_PREFIX MATCHES "CMAKE_(SHARED|MODULE|EXE)_LINKER_FLAGS")
    foreach (config SHARED MODULE EXE)
      set(CMAKE_${config}_LINKER_FLAGS_INIT "${QT_LINKER_FLAGS}")
    endforeach()
  endif()

  _cmake_initialize_per_config_variable(${ARGV})
endfunction()

set(XCB_PATH_VARIABLE ${TARGET_SYSROOT})

set(GL_INC_DIR ${TARGET_SYSROOT}/usr/include)
set(GL_LIB_DIR ${TARGET_SYSROOT}:${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu:${TARGET_SYSROOT}/usr:${TARGET_SYSROOT}/usr/lib)

set(EGL_INCLUDE_DIR ${GL_INC_DIR})
set(EGL_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libEGL.so)
set(EGL_LIBRARY_DIRS ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu)

set(OPENGL_INCLUDE_DIR ${GL_INC_DIR})
set(OPENGL_opengl_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libOpenGL.so)

set(GLESv2_INCLUDE_DIR ${GL_INC_DIR})
set(GLIB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libGLESv2.so)

set(GLESv2_INCLUDE_DIR ${GL_INC_DIR})
set(GLESv2_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libGLESv2.so)

set(gbm_INCLUDE_DIR ${GL_INC_DIR})
set(gbm_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libgbm.so)

set(Libdrm_INCLUDE_DIR ${GL_INC_DIR})
set(Libdrm_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libdrm.so)

set(XCB_XCB_INCLUDE_DIR ${GL_INC_DIR})
set(XCB_XCB_LIBRARY ${XCB_PATH_VARIABLE}/usr/lib/aarch64-linux-gnu/libxcb.so)
```

以上で準備は完了です。

続きは[ビルド＆実行編](https://qiita.com/tkhshmsy/items/604ccf95be93ecc674c2)です。