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

2021年7月3日C++,CMake,Linux,MSVC,Windows,WSL

Windows + Linuxのマルチプラットフォーム開発ではWindows版はVisual Studioで開発して、Linuxはまた別のIDE(EclipseやNetBeans)を使うかLinuxだけgcc等のCUIツールに頼る事が多いと思います。

最近ではLinux版の開発にVisual StudioやVisual Sduio Codeが使えるようになっています。そして、CMakeを使う事でNinjaやVisual Studio用のビルド環境を構築する事も出来ます。

最終的にはWSL + CMake Presetsを用いて、Windowsで動作しているVisual Studio、Visual Studio CodeからWSLで動作しているubuntuでプログラムを動作させてデバッグさせるというクロスプラットフォーム開発環境を構築する事が可能となっています。

CMakeからVisual Studio用のソリューション・プロジェクトを出力する事も可能です。LLVM Project等多くのオープンソースではそうしています。

ですが、私はWindows版の開発は通常のVisual Studio + MSBuildで開発を行い、Linux用の開発はWSL + CMakeで開発しています。

理由はVisual Studioをメインにして開発をしているので、変更がある度にCMakeでプロジェクトを出力し直すのが手間だからです。プロジェクトを出力し直すとプロジェクトのリロードをします。そうすると良く表示がバグります。そこで、プロジェクトを一度削除して作り直すと設定したデバッグ用の引数等が消えたりするので不便です。

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

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

まとめ

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

プロジェクトが大きくなってくるとMSBuildはビルドが遅いのが気になってくるので、MSVCでコンパイルしたい時もNinjaでビルドするのも良いかと思います。コンパイラの設定をMSVCにするにはvarsall.datを読み込んでcmakeを起動するだけです。こちらの記事が参考になります。Ninja + PDBでビルドしてもVisual Studioでデバッグ出来ます。