CMake - Qt5 / Qt6の互換性
概要
Qt 5およびQt 6のCMakeコマンドのセマンティクスは、ほぼ互換性がある。
しかし、Qt 5.14以前では、インポートされたQtライブラリのターゲットとコマンドは、全て名前の一部としてバージョン番号を含んでいた。
そのため、Qt 5とQt 6の両方で動作するCMakeコマンドを記述することは煩雑であった。
Qt 5.15以降では、バージョンレスターゲットおよびバージョンレスとコマンドを導入しており、異なるQtのバージョンにほとんど依存しないCMakeコマンドが記述できるようになっている。
Qt 5 / Qt 6ライブラリの検索
システムからQt 5 / Qt 6モジュールを検索する場合、以下に示すように記述する。
これは、Qt 6への移行を容易にしつつ、Qt 5を使用しているシステムとの互換性も維持することができる。
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network Xml)
- まず、Qt 6モジュールを探す。
- Qt 6モジュールが見つからない場合、Qt 5モジュールを探す。
- いずれかが存在する場合、該当するバージョンのCore、Network、Xmlコンポーネントを探す。
- REQUIREDフラグがあるため、どちらのバージョンも存在しない場合はエラーになる。
つまり、システムにQt 6とQt 5の両方がインストールされている場合、Qt 6が優先して使用される。
バージョンレスターゲット
既存のインポートターゲットに加えて、Qt 5.15以降ではバージョンレスターゲットが使用できる。
例えば、Qt Coreライブラリとリンクする場合、Qt6::CoreとQt::Coreの両方を参照することができる。
バージョンレスターゲットはデフォルトで定義されている。
もし、バージョンレスターゲットを無効にする場合は、最初のfind_package
コマンドの実行前に、QT_NO_CREATE_VERSIONLESS_TARGETS
を設定する必要がある。
QT_NO_CREATE_VERSIONLESS_TARGETS
- Qt::で始まるインポートされたターゲットを隠す。
- 代わりに、Qt6::で始まるターゲットを使用する必要がある。
※注意
例えば、インポートされたQt::Coreターゲットには、Qt6::Coreターゲットで利用可能なターゲットプロパティは無いことに注意する。
以下の例では、まず、Qt 6ライブラリを検索する。
Qt 6ライブラリの検索に失敗した場合は、Qt 5.15ライブラリを検索する。
これにより、Qt 5 / Qt 6に関係なく、インポートされたQt::Coreターゲットを使用することができる。
# バージョンレスターゲットを有効にする場合
# デフォルトで有効なため、設定は不要である
find_package(Qt6 COMPONENTS Core)
if (NOT Qt6_FOUND)
find_package(Qt5 5.15 REQUIRED COMPONENTS Core)
endif()
add_executable(myApp
# ...略
)
target_link_libraries(myApp PRIVATE
Qt::Core
)
# バージョンレスターゲットを無効にする場合
set(QT_NO_CREATE_VERSIONLESS_TARGETS OFF)
find_package(Qt6 COMPONENTS Core)
# ...略
バージョンレスコマンド
Qt 5.15以降、Qtモジュールはバージョンレスのコマンドも提供している。
例えば、Qt5とQt6のどちらを使用していても、qt_add_translation
コマンドを使用して、翻訳ファイルをコンパイルすることができる。
最初のfind_package
コマンドの実行の前に、QT_NO_CREATE_VERSIONLESS_FUNCTIONS
を設定して、バージョンレスコマンドの作成を防ぐ。
QT_NO_CREATE_VERSIONLESS_FUNCTIONS
- qt_で始まるコマンドを非表示にして、qt6_で始まるバージョン管理されたコマンドのみを残す。
このマクロを定義することで、Qtのバージョン付きのシンボルのみが使用されるようになる。
これは、特にQtの異なるバージョンを混在させる場合や明示的にバージョン管理を行う場合に有用である。
以下の例では、find_package
コマンドの前にset
コマンドでQT_NO_CREATE_VERSIONLESS_FUNCTIONS
をON
またはTRUE
に設定している。
これにより、Qtのバージョンなしのシンボルが生成されないようになる。
CMake 3.16以降が必要な場合があるため、cmake_minimum_required
コマンドを適切に設定する必要がある。
cmake_minimum_required(VERSION 3.16)
# find_packageコマンドの前にQT_NO_CREATE_VERSIONLESS_FUNCTIONSを設定する
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS ON)
project(MyQtProject)
# 使用するQtライブラリ
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
# 実行ファイルのソースファイルおよびヘッダファイル
add_executable(MyApp main.cpp)
# Qtライブラリのリンク
target_link_libraries(MyApp Qt6::Core Qt6::Widgets)
Qt 5とQt 6の混在
Qt 5およびQt 6の両方を1つのCMakeファイルに記述する場合、バージョンレスターゲットおよびバージョンレスコマンドは、find_package
コマンドで検索された最初のQtバージョンが暗黙的に参照される。
ただし、1つのライブラリや実行ファイルにQtのバージョンを混在させることはサポートされていない。
バージョンを明示的にするには、最初のfind_package
コマンドを実行する前に、CMake変数QT_DEFAULT_MAJOR_VERSION
を設定する必要がある。
QT_DEFAULT_MAJOR_VERSION
- Qt 5プロジェクトとQt 6プロジェクトが混在している場合、qt_コマンドで進むQtのバージョンを指定する。
find_package
コマンドを実行する前に、Qt 5かQt 6のいずれかに設定する必要がある。- 5に設定する場合、qt_で始まるコマンドはqt5_で始まるコマンドを呼び出す。
- 6に設定する場合、qt_で始まるコマンドはqt6_で始まるコマンドを呼び出す。
- 設定されていない場合、最初の
find_package
コマンドを実行すると、デフォルトのバージョンが定義される。
レガシーなQt 5のサポート
Qt 5.14以前のバージョンもサポートする必要がある場合は、CMake変数に現在のバージョンを格納することで対応することができる。
以下の例では、find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)では、まず、Qt 6を検索、見つからない場合はQt 5をQTという名前で検索している。
いずれかの検索に成功した場合は、CMake変数QT_VERSION_MAJOR
は5または6のいずれかに定義される。
次に、Qt${QT_VERSION_MAJOR}という名前をオンザフライで作成することにより、決定されたQtバージョンのパッケージを再度読み込む。
これは、CMAKE_AUTOMOCがパッケージ名がQt5あるいはQt6であることを期待しており、異なる場合はエラーを表示するためである。
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
add_executable(myApp
# ...略
)
target_link_libraries(myApp PRIVATE
Qt${QT_VERSION_MAJOR}::Core
)
同様に、インポートするQtライブラリ名も指定することができる。
target_link_libraries
コマンドにQt${QT_VERSION_MAJOR}::Widgetsと記述することにより、Qt5::WidgetsまたはQt6::Widgetsのいずれかのライブラリをリンクすることができる。
推奨事項
- 可能であれば、CMakeコマンドのバージョンレスバリアントを使用する。
- 同じプロジェクトでQt 5およびQt 6をサポートする必要がある場合を除き、バージョン付きのターゲットを使用する。
- バージョンレスターゲットを使用しなければならない場合は、バージョンレスターゲットを使用する際のデメリットに注意する。
- Qt 5.15より古いバージョンのQt 5をサポートする必要がある場合や、
QT_NO_CREATE_VERSIONLESS_FUNCTIONS
やQT_NO_CREATE_VERSIONLESS_TARGETS
が定義されているコンテキストでCMakeコードがロードされるかどうかを制御できない場合は、
バージョン管理されたバージョンのCMakeコマンドやターゲットを使用する。
この場合でも、変数を通して実際のコマンド名やターゲット名を決定することにより、コードを簡素化することができる。
バージョンレスターゲットのデメリット
バージョンレスターゲットには、いくつかのデメリットがある。
バージョンレスターゲットはALIASターゲットであり、バージョン付きターゲットのターゲットプロパティが無い。
プロジェクトは、バージョンレスターゲットを公開するターゲットをエクスポートしてはならない。
例えば、他のプロジェクトにより消費されるライブラリは、バージョンレスターゲットに対して公にリンクするターゲットをエクスポートしてはならない。
これは、推移的依存関係が壊れたり、そのライブラリのユーザがQt 5とQt 6のターゲットを不本意に混在させたりする可能性がある。
WindowsのUnicodeサポート
Qt 6では、Qtモジュールに対してリンクするターゲットに対して、UNICODE
と_UNICODE
コンパイラ定義がデフォルトで有効化されている。
これは、qmakeコマンドの動作と同様であるが、Qt 5のCMake APIの動作と差異がある。
ターゲット上において、qt_disable_unicode_defines
コマンドを実行して、UNICODE
と_UNICODE
コンパイラ定義を無効にする。
find_package(Qt6 COMPONENTS Core)
add_executable(myApp
# ...略
)
qt_disable_unicode_defines(myApp)