WindowsとLinuxの両方で動作するプログラムを開発する時には、WindowsでのIDEにはVisual Studioで利用し、LinuxではEclipseやNetBeans等の別のIDEを利用するか、Linuxだけgdb、lldb等のCUIツールを利用する事が多いかと思います。
Visual Studioはとても便利なIDEですので、出来ればLinux向けのアプリケーション開発もVisual Studioを使えるようにならないかなと常々思っていました。
少し前からLinux用の対応を追加してましたが、それはWindows用とは別にLinux専用のプロジェクトが必要でしたし、デバッグもSSH経由でリモート接続が必要で色々と不便でした。
CMake PresetsとWSLを組み合わせてLinux用アプリケーションを開発する
WSLを用いる事でWindowsでLinuxを動かす事が出来ます。そして、CMakeのPresets機能を使う事でVisual SudioとVisual Studio CodeがLinux用のプラットフォーム開発環境を認識するようになります。
Visual Studio、もしくはVisual Studio CodeからWSLのubuntuでプログラムを実行・デバッグする事が出来ます。
CMake Presets + WSLを用いた開発環境では、Visual Studioを用いてLinux向けのアプリケーションを、Windows向けのアプリケーション開発と遜色なく行う事が出来ます。
Ninjaでビルド時間を短縮
CMakeに対応する事でMSBuildだけでなくNinjaでビルドを行う事も出来ます。プロジェクトの規模にもよりますが、NinjaはMSBuildに比べて圧倒的にビルド時間を短縮する事が出来ます。例えば、LLVM ClangをMSBuildでビルドするのとNinjaでビルドするのとでは二倍以上Ninjaの方が速いです。
MSBuildとNinjaでLLVM Clangをビルドする方法はこちらの記事で紹介しています。
WindowsはVisual Studioプロジェクト、LinuxはCMakeがお勧め
CMakeからVisual Studio用のソリューション・プロジェクトを出力する事も可能です。LLVM Project等多くのオープンソースではそうしています。
私はWindows版はVisual Studioでプロジェクト管理を行い、Linux版だけをCMakeでプロジェクト管理しています。
理由はVisual Studioをメインにして開発をしているので、変更がある度にCMakeでプロジェクトを出力し直すのが手間だからです。プロジェクトを出力し直すとVisual Studioがプロジェクトのリロードが必要ですが、リロードすると表示がバグる事が多いです。
Linux版の簡単な修正であればVisual Studio CodeでNinjaのビルド設定を出力してビルド・実行・デバッグするのがお手軽で便利です。
また、MSVCとClangではコンパイル・リンクオプションが違うのでCMakeにMSVC・Visual Studio用の記述を書かないといけなくなるので管理が手間です。単純にLinuxのビルドだけをCMakeLists.txtに書いた方がシンプルで良いです。
そのような理由からWindowsはVisual Studio、LinuxはCMakeと割り切って管理しています。
開発環境
- Windows 10 Pro Insider Build 21376
- Visual Studio 2019 Preview (16.10.0)
- WSL2 ubuntu 20.04
- Visual Studio Code 1.56
- CMake Tools 1.7.2
- CMake 3.20.2
WSLでのUbuntuのインストール方法はこちらの記事で紹介しています。Windows 10のバージョン次第ではコマンド1つで完了します。私はそこで楽をしたかったのでWindows Insider ProgramをDevで使っていますが、更新頻度高いのが嫌ですね…
CMake Presets
CMakeのPresets機能はCMake 3.19, 3.20から使用できるようになった機能です。
CMake PresetsとはCMakePresets.jsonに”configurePresets“と”buildPresets“を記述する事で、CMakeのConfigureとBuild、それぞれの設定を共有する事が出来る機能です。
複数人での開発やCIでの実行がとても楽になります。
Clang + Linux環境用の設定を見てみましょう。
{
"version": 2,
"configurePresets": [
{
"name": "clang-linux",
"displayName": "Configure with Clang for Linux",
"description": "Sets Ninja generator, build and install directory",
"generator": "Ninja Multi-Config",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_MAKE_PROGRAM": "ninja",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/clang.cmake",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}"
}
}
],
"buildPresets": [
{
"name": "debug",
"description": "Debug build with Clang for Linux",
"displayName": "Debug Clang Linux",
"configurePreset": "clang-linux",
"configuration": "Debug"
},
{
"name": "release",
"description": "Release build with Clang for Linux",
"displayName": "Release Clang Linux",
"configurePreset": "clang-linux",
"configuration": "Release"
}
]
}
configurePresets
configurePresetsの中でLinux環境でClangを使う時の設定を記述しています。
cmakeでconfigureを実行するには、コマンドが簡潔になります。
cmake --preset clang-linux
generatorの項目はcmake -G “Ninja Multi-Config”と同じ意味を持ちます。
binaryDirはbuildフォルダを指定します。out-of-souce buildにしておかないとビルドデータがソースコードに混じって大変な事になります。
cacheVariablesはCMAKE_XXXで指定する項目を記述します。
clang.cmakeはtoolchain指定用の項目が書いてあります。DebugとReleaseビルドを作成したいのでそれぞれ項目が書いてあります。
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER /usr/bin/clang-11)
set(CMAKE_CXX_COMPILER /usr/bin/clang++-11)
set(CMAKE_AS /usr/bin/llvm-as-11)
set(CMAKE_NM /usr/bin/llvm-nm-11)
set(CMAKE_RANLIB /usr/bin/llvm-ranlib-11)
set(CMAKE_OBJDUMP /usr/bin/llvm-objdump-11)
set(CMAKE_LINKER /usr/bin/lld-link-11)
set(CMAKE_CXX_FLAGS "-Wall")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g" )
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
CMAKE_CONFIGURATION_TYPESはcacheVariablesで指定しても良いのですが、CMakeLists.txtの中で指定しています。
cmake_minimum_required(VERSION 3.20)
project(CMakePresets C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CONFIGURATION_TYPES Debug;Release)
set(sources main.cc)
add_executable(Sample ${sources})
if(UNIX)
target_link_libraries(Sample PRIVATE stdc++)
endif()
buildPresets
buildPresetsは元になるconfigurePresetを選んでconfigurationを指定するだけです。
Debug、Releaseビルドの切り替え用でしかないので凄くシンプルな記述になります。
buildPresetを使うってビルドするにはconfigureで作成したフォルダとbuildPresetを指定します。
cmake --build build/clang-linux --preset debug
Multi-Config対応のgeneratorの時には–configでconfigurationを指定できます。私はこちらをよく使うのでbuildPresetは書かなくても良いんじゃないかと思っています。
cmake --build build/clang-linux --config Debug
Visual Studio 2019の設定(Version 16.10.0以降)
記事執筆時点では、Preview版のVisual Studioでしか利用出来ていませんが、Visual Studio 2019 Version 16.10.0以降でCMakePresetsに対応しています。
設定画面で、”Use CMakePresets.json to drive CMake configure, build, and test.”にチェックを入れます。そして、CMakePresets.jsonを配置すれば認識します。
Visual Sudioを起動して”フォルダを開く”を選んで、CMakeのrootとなるフォルダを選択して開きます。
後は通常のVisual Studioでの開発と同じです。WSLで動作させている”WSL:ubuntu”を選んで、次にconfigurePresetsの”clang-linux”、”buildPresets”のdebugをそれぞれ選びます。最後にスタートアッププログラム(デバッグ対象プログラム)を選択します。
Visual Studioの開発環境でびっくりしたのが、Windowsに配置したソースコードでビルド・デバッグ出来る点です。後述するVisual Studio CodeはWSL ubuntu上にソースコードを展開する必要があります。
Visual Studio Codeの設定
CMake Presetsを利用するには、Visual Studio Code ExtentionのCMake Tools 1.7以降が必要です。
CMake Toolsのデフォルト設定では、Use CMake Presetsはautoになっています。autoの時はCMakePresets.jsonが在れば自動的に認識します。
WSLへの接続
Visual Studio CodeからWSLに接続する為に必要なRemote – WSL拡張をインストールします。
WSL + Visual Studio Codeでの開発ではWindowsに置いてあるフォルダをWSL経由で開く事になります。/mnt/以下にWindowsのドライブがマウントされているのでそこから選択します。
dドライブのsamplesディレクトリであれば/mnt/d/samplesとなります。
Visual Studio CodeでのWSLへの接続は、ウィンドウ左の“Remote Explorer”で選ぶ事が出来ます。
それ以外にもCtrl + Shift + Pを押して“Remote-WSL: New WSL Window“を選択するか、ウィンドウ左下の緑色の部分をマウスでクリックして選択する事も出来ます。
WSLに接続したらCMake Toolsのフォルダを開くを選択してCMakeのrootフォルダを開きます。CMake ToolsがCMakePresetsを認識するとCMake Presetsのコマンドが使えるようになります。
configurePreset、buildPresetの選択
最初はConfigureが必要なので、Ctrl + Shift + Pを押して”CMake: Select Configure Preset“を選択してConfigureを実行します。
次にconfigurePresetとしてclang-linuxを選択します。
configurePresetの選択が完了したのでConfigureを実行します。Ctrl + Shift + Pを押して”CMake: Configure”を実行します。
続いてbuildPresetを選択します。Ctrl + Shift + Pを押して”CMake: Select build Preset“を選択します。
Debug、Releaseのどちらかを選択します。
これでビルドする為の環境が揃ったので、CMAKE: PROJECT OUTLINEにプロジェクトの一覧が表示されます。ここからはGUIでビルド実行も出来ますが、コマンドでもビルド実行が可能です。
コマンドでのビルド実行は、Ctrl + Shift + Pを押して”CMake: Build Target“を選択します。
ビルドターゲットの一覧が表示されるので選択して実行するとビルドを開始します。
また、Visual Studio Codeの画面下にある青いバー部分もCMakePresetsのコマンドに対応しており、それぞれの項目を選択する事でも各コマンドを実行できるようになっています。
Debugger起動
Visual Studio CodeはDebuggerでのデバッグが可能です。WSL環境でもその機能が使えるので紹介します。LLDBでのデバッグにはCodeLLDB、gdbでのデバッグにはC/C++のVisual Studio Code Extentionのインストールが必要です。
最初にWSLでlldbとgdbをインストールします。
sudo apt-get install lldb gdb
次に.vs/lunch.jsonにlldbとgdbでのデバッグ起動を記述します。program指定にはcmakeのターゲット変数を用いると楽です。
“program”: “${command:cmake.launchTargetPath}”
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Clang Linux LLDB Debug",
"type": "lldb",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"cwd": "${workspaceFolder}"
},
{
"name": "Clang Linux GDB Debug",
"type": "cppdbg",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
最後にDebuggerを選択してF5を押すとデバッグ起動します。今回はClangでコンパイルしているのでlldbを選択します。
Visual Studioと同じbreakpointやWatch、Call Stack等基本的な機能が揃っています。
Azure Pipelinesへの対応
最初にも説明しましたが、CMake PresetsはCIでのConfigure、Buildの実行が楽になるという利点があります。それは共通のConfigure、Build設定を使う事が出来るので設定ミスが無いからです。
trigger:
- master
stages:
- stage: Linux
jobs:
- job:
pool:
vmImage: 'ubuntu-latest'
steps:
- bash: brew install ninja cmake
displayName: 'Install Ninja and CMake'
- bash: cmake -S $(Build.SourcesDirectory)/CMakePresets --preset clang-linux
displayName: 'CMakePresets Configure clang-linux'
- bash: cmake --build $(Build.SourcesDirectory)/CMakePresets/build/clang-linux --config Debug
displayName: 'CMakePresets Build Debug clang-linux'
- bash: cmake --build $(Build.SourcesDirectory)/CMakePresets/build/clang-linux --config Release
displayName: 'CMakePresets Build Release clang-linux'
cmakeでconfigure > build という流れでコマンドの引数が少ないのが分かるかと思います。
Azure Pipelinesのtask機能のCMake@1はバージョンが古いので、cmakeをインストールして利用します。
Azure Pipelinesの環境はWSLのubuntuと違うので、コマンドのパス指定に難儀しました。
例えばclang-11は/usr/bin/clang-11はNGで/bin/clang-11はOKです。他にもninjaは/bin/ninjaはNGでninjaはOKです。一度分かってしまえばなんて事はないのですが、意外と変な所で躓きますね。
Sample
GitHub + Azure Pipelinesでブログ用に作ったサンプル環境を公開しています。
まとめ
Visual Studio、Visual Studio Code共にWindowsに配置している単一のリポジトリでWindowsとLinuxのビルド、実行、デバッグが出来るのでかなり楽です。
Visual StudioはPreview版ですが簡単にWSLでの開発環境を構築する事が出来ます。IntellisenseがConfigureの切り替えにきちんと対応しているのも良い所ですね。Visual Studoはフォルダを開くと結構処理が重いのが気になります。長時間作業していると徐々に重くなっていくので定期的に再起動が必要です。
Visual Studio Codeは手順が多いですが面倒なのは最初の1回だけです。Visual Studio Codeの魅力は起動が早く色んな操作をコマンドで実行できるのでマウスを動かす回数が少ない点にあると思っています。
私はどちらの環境も使っています。WindowsとLinuxのアプリケーションで通信する時には、WindowsをVisual Studioで起動してLinuxをVisual Studio Codeで起動して動作確認をするのに使ったりととても便利です。