WSLとCMake PresetsでLinuxアプリケーションのクロスプラットフォーム開発環境を構築

C++,CMake,Linux,MSVC,Windows,WSL

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(7)

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に対応しています。

Visual Studio 2019 Preview 16.10.0 CMakePresets settings
Visual Studio 2019 Preview 16.10.0 CMakePresets settings

設定画面で、"Use CMakePresets.json to drive CMake configure, build, and test."にチェックを入れます。そして、CMakePresets.jsonを配置すれば認識します。

Visual Sudioを起動して"フォルダを開く"を選んで、CMakeのrootとなるフォルダを選択して開きます。

Visual Studio 2019 Preview 16.10.0 CMakePresets debugging
Visual Studio 2019 Preview 16.10.0 CMakePresets debugging

後は通常のVisual Studioでの開発と同じです。WSLで動作させている"WSL:ubuntu"を選んで、次にconfigurePresetsの"clang-linux"、"buildPresets"のdebugをそれぞれ選びます。最後にスタートアッププログラム(デバッグ対象プログラム)を選択します。

Visual Studio 2019 Preview 16.10.0 CMakePresets select startup program
Visual Studio 2019 Preview 16.10.0 CMakePresets select startup program

Visual Studioの開発環境でびっくりしたのが、Windowsに配置したソースコードでビルド・デバッグ出来る点です。後述するVisual Studio CodeはWSL ubuntu上にソースコードを展開する必要があります。

Visual Studio Codeの設定

Visual Studio Code Extention CMake Tools 1.7.2
Visual Studio Code Extention CMake Tools 1.7.2

CMake Presetsを利用するには、Visual Studio Code ExtentionのCMake Tools 1.7以降が必要です。

CMake Toolsのデフォルト設定では、Use CMake Presetsはautoになっています。autoの時はCMakePresets.jsonが在れば自動的に認識します。

Visual Studio Code Extention: CMake Tools settings Use CMake Presets
Visual Studio Code Extention: CMake Tools settings Use CMake Presets

WSLへの接続

Visual Studio Code Extention: Remote - WSL
Visual Studio Code Extention: Remote – 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"で選ぶ事が出来ます。

Visual Studio Code: Remote Explorer: WSL TARGETS
Visual Studio Code: Remote Explorer: WSL TARGETS

それ以外にもCtrl + Shift + Pを押して“Remote-WSL: New WSL Window“を選択するか、ウィンドウ左下の緑色の部分をマウスでクリックして選択する事も出来ます。

Visual Studio Code: Connect to WSL
Visual Studio Code: Connect to WSL

WSLに接続したらCMake Toolsのフォルダを開くを選択してCMakeのrootフォルダを開きます。CMake ToolsがCMakePresetsを認識するとCMake Presetsのコマンドが使えるようになります。

configurePreset、buildPresetの選択

最初はConfigureが必要なので、Ctrl + Shift + Pを押して"CMake: Select Configure Preset“を選択してConfigureを実行します。

Visual Studio Code Extention: Cmake Tools: CMake: Select Configure Preset
Visual Studio Code Extention: Cmake Tools: CMake: Select Configure Preset

次にconfigurePresetとしてclang-linuxを選択します。

Visual Studio Code Extention: Cmake Tools: CMake: Select Configure Preset: clang-linux
Visual Studio Code Extention: Cmake Tools: CMake: Select Configure Preset: clang-linux

configurePresetの選択が完了したのでConfigureを実行します。Ctrl + Shift + Pを押して"CMake: Configure"を実行します。

Visual Studio Code Extention: Cmake Tools: CMake: Configure
Visual Studio Code Extention: Cmake Tools: CMake: Configure

続いてbuildPresetを選択します。Ctrl + Shift + Pを押して"CMake: Select build Preset“を選択します。

Visual Studio Code Extention: Cmake Tools: CMake: Select Build Preset
Visual Studio Code Extention: Cmake Tools: CMake: Select Build Preset

Debug、Releaseのどちらかを選択します。

Visual Studio Code Extention: Cmake Tools: CMake: Select Debug or Release build
Visual Studio Code Extention: Cmake Tools: CMake: Select Debug or Release build

これでビルドする為の環境が揃ったので、CMAKE: PROJECT OUTLINEにプロジェクトの一覧が表示されます。ここからはGUIでビルド実行も出来ますが、コマンドでもビルド実行が可能です。

Visual Studio Code Extention: CMake Tools: CMAKE: PROJECT OUTLINE
Visual Studio Code Extention: CMake Tools: CMAKE: PROJECT OUTLINE

コマンドでのビルド実行は、Ctrl + Shift + Pを押して"CMake: Build Target“を選択します。

Visual Studio Code Extention: CMake Tools: CMake: Build Target
Visual Studio Code Extention: CMake Tools: CMake: Build Target

ビルドターゲットの一覧が表示されるので選択して実行するとビルドを開始します。

Visual Studio Code Extention: CMake Tools: Select Build Target
Visual Studio Code Extention: CMake Tools: Select Build Target

また、Visual Studio Codeの画面下にある青いバー部分もCMakePresetsのコマンドに対応しており、それぞれの項目を選択する事でも各コマンドを実行できるようになっています。

Visual Studio Code: 画面下のCMake Tools選択メニュー
Visual Studio Code: 画面下のCMake Tools選択メニュー

Debugger起動

Visual Studio CodeはDebuggerでのデバッグが可能です。WSL環境でもその機能が使えるので紹介します。LLDBでのデバッグにはCodeLLDB、gdbでのデバッグにはC/C++のVisual Studio Code Extentionのインストールが必要です。

Visual Studio Code Extention: CodeLLDB
Visual Studio Code Extention: CodeLLDB
Visual Studio Code Extention: C/C++
Visual Studio Code Extention: C/C++

最初に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 Code: デバッグ起動の選択
Visual Studio Code: デバッグ起動の選択

Visual Studioと同じbreakpointやWatch、Call Stack等基本的な機能が揃っています。

Visual Studio Code: WSL lldb debugging
Visual Studio Code: WSL lldb debugging

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でブログ用に作ったサンプル環境を公開しています。

https://github.com/meltybk/samples/tree/master/CMakePresets

https://melty.visualstudio.com/Sample/_build

まとめ

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で起動して動作確認をするのに使ったりととても便利です。