🐯

toolchain利用cmakeでdos,win,mac,linux向ビルド

2025/01/19に公開

前回の ncurses/PDCurses サンプル を、cmake でビルドできるようにしてみる。

cmake で対応していないクロスコンパイラの場合にツールチェイン・ファイルを使うので、いっそ、コンパイラ&ターゲット環境ごとにツールチェイン・ファイルに分けて対応。そのぶん CMakeLists.txt はスッキリする、かも。

今までに watcomdjgppia16-elf-gcc でツールチェイン・ファイルに分けての cmake を試していたので、その延長で。

今回は前回試した ncurses / PDCurses のライブラリが使える環境ということで、
ターゲット環境は win64 win32 mac linux dos32 dos16

win は Windows10 x64、linux は wsl ubuntu、mac は macos 15.2、で作業&実行確認を行い、dos関係は dosbox-x (一部 msdos player) で確認。

ia16-elf-gcc の名を出したけれど、今回は未対応。

サンプル・フォルダ

cursesサンプル の hello.c や PDCurses フォルダを、以下のように配置。
※ ビルド実行後にできるフォルダを含む。

curses_hello/
  CMakeLists.txt
  src/
    hello.c                       # サンプルソース.
  bld/
    bld.bat  bld.sh               # ビルドスクリプト.
    setcc.bat                     # コンパイラ設定.
    [ツールチェイン名]/           # [生成] ターゲットごとのビルド作業フォルダ.
  toolchain/
    *-toolchain.cmake             # ツールチェイン別の設定.
  thirdparty/
    install_pdcurses.bat          # pdcurses インストール用バッチ.
    PDCurses/                     # [生成] git clone した PDCurses フォルダ.
    include/                      # [生成] pdcursesのヘッダ. アプリ側include用.
    lib/[ツールチェイン名]/       # [生成] 生成された pdcursesライブラリ置き場
  bin/
     [ツールチェイン名]           # [生成] ターゲット別に生成された実行ファイルを置く.

外部ライブラリは thirdparty フォルダに置き、ヘッダは include に、ライブラリは lib/[ツールチェイン名] に集めて、アプリ側はそれを参照するような使い方を想定。
※ cmake 推奨のやり方から外れるけれど、cmakeするたびに滅多に変わらない外部ライブラリをビルドするのは避けたかったので。

実際にビルドできる環境としては

サンプル: https://github.com/tenk-a/samples/blob/cmake_toolchain_and_pdcurses

に今回の環境を置いている。
試す場合はダウンロードして curses_hello フォルダを見て、と。

ビルド手順

まず目的のビルドが行える Cコンパイラをコンソールで使えるようにする。(win環境は前回の setcc.bat使用)

手動で行う場合

  1. ncurses / PDCurses を使えるようにする
  • mac linux(unix) は ncurses でコンパイルできる状態である前提
  • win,dos用では thirdparty フォルダに PDCurses フォルダがなければ git clone
    git clone https://github.com/wmcbrine/PDCurses
    
  • win,dos用では pdcurses ライブラリがなければ、install_pdcurses.bat 実行
    へッダを thirdparty/include フォルダにコピー
    make してできたライブラリを thirdparty/lib/[ツールチェイン名] フォルダにコピー
    gcc系の場合は libpdcurses.a に変名
    ※ install_pdcurses.bat でなく手作業で行う場合は前回のPDCursesのmakeを参考
  1. ビルド環境生成
    cmake -G "..."
          -DCMAKE_TOOLCHAIN_FILE=toolchain/[ツールチェイン名]-toolchain.cmake
          -B bld/[ツールチェイン名]
          .
    
  2. ビルド
    cmake --build bld/[ツールチェイン名]
    
  3. bin/[ツールチェイン名]/ フォルダに実行ファイルをコピー
    cmake --install bld/[ツールチェイン名]
    

ただコンパイラに対応した -G "..." の指定とか細々面倒なので、上記を行うスクリプト

を用意。

たとえば

bld vc-win64-md
bld watcom-dos-16-s
bash bld.sh mac

等でビルド、bin/[ツールチェイン名]/hello ができる。

できた実行ファイルの確認は、ホスト環境と同じならそのままコンソールで実行確認。
16bit/32bit dos 用は dosbox-x で確認。(16bit dos は msdos player でも)

ツールチェイン・ファイル

ツールチェイン・ファイルは、クロスコンパイラ等 cmake 未対応のコンパイラを対応させるのに使う。

cmake -DCMAKE_TOOLCHAIN_FILE=ツールチェイン・ファイル .  

のように指定。
詳しくは cmake-toolchains(7)たのしい組み込みCMake あたりを参考。

今回のサンプルでは、生成したいコンパイラ&実行環境ごとに、
  [ツールチェイン名]-toolchain.cmake
ファイルを用意。
toolchain フォルダに置いている。

ツールチェイン名は、CMakeLists.txt 側でコンパイラやターゲットOSの判定に使うつもりなので、そのような文字列を含むように命名している。

現在のツールチェイン名は
vc-win64 vc-win64-md vc-win32 vc-win32-md
mac linux mingw-win64 mingw-win32 djgpp-dos32
watcom-win32 watcom-dos32 watcom-dos16-s
(実行未確認だが vc-winarm64 vc-winarm )

ツールチェイン・ファイルの制限

ツールチェイン・ファイルは、cmake 実行の初期に読み込まれることで、project() でのビルド環境の設定が行われる前に、Generater(-G)設定の一部を書き換えての別コンパイラ対応が行える。(たぶん)

逆にいうとツールチェイン・ファイルでは project() で確定する CMAKE_ 変数が使えない。
CMAKE_SOURCE_DIR が使えない。が CMAKE_CURRENT_LIST_DIR は使える。

また特別な変数以外は set で CACHE 指定していないと CMakeLists.txt 側に渡せない。

※ なんとなく仕組みわかってきたような気はするけれど、まだまだよくわからず。

TOOLCHAIN_ 変数

今回のサンプルの仕組みとして、TOOLCHAIN_ で始まるキャッシュされた変数を用意し、CMakeLists.txt 側で参照できるようにしている。
コンパイラや環境固有の、ソース・ファイル、コンパイラ・オプション、includeフォルダ、ライブラリ・フォルダ、ライブラリの設定が必要なら、以下の変数に入れて CMakeLists.txt 側に伝える。

  • TOOLCHAIN_ADD_SRCS : 追加のソース・ファイル
  • TOOLCHAIN_ADD_OPTS : 追加のコンパイラ・オプション
  • TOOLCHAIN_ADD_INCLUDE_DIRS : 追加の include フォルダ
  • TOOLCHAIN_ADD_LINK_DIRS : 追加のライブラリ・フォルダ
  • TOOLCHAIN_ADD_LIBS : 追加のライブラリ

※ 現状、vc で TOOLCHAIN_ADD_LIBS しか使っていない。

CMakeLists.txt

ツールチェイン・ファイル側で、コンパイラやターゲット環境の設定を行い、CMakeLists.txt はそれ前提にしている。

CMakeLists.txt は比較的すっきりしたと思うが、TOOLCHAIN_変数のやり取りが最低限あるため、ただの hello サンプルとしてはゴツい……

内容は、ざっくりと

  • 変数 SRCS:ソース・ファイル、INC_DIRS:includeディレクトリ、OPTS:コンパイラオプション、LIB_DIRS:ライブラリ・ディレクトリ、LIBS:ライブラリ・ファイル に基本的な設定を行う。
  • CMAKE_TOOLCHAIN_FILE 変数からフォルダや拡張子付近を取り除き TOOLCHAIN_NAME を取得
  • TOOLCHAIN_NAME 等を見ながら各種コンパイラ&環境の個別設定や外部ライブラリの準備
  • SRCS,INC_DIRS,OPTS,LIB_DIRS,LIBS 及びそれに対応する TOOLCHAIN_??? 変数を合わせて、実際のビルドにつながる add_executabletarget_??? を設定

を行っている。

cmake_minimum_required(VERSION 3.24)

# cmake:プロジェクト.
project(hello C)

# ソース・ディレクトリのパス.
set(SRC_DIR "${CMAKE_SOURCE_DIR}/src")
# thirdparty ディレクトリのパス.
set(THIRDPARTY_DIR "${CMAKE_SOURCE_DIR}/thirdparty")

# ソース・ファイル.
set(SRCS
  ${SRC_DIR}/hello.c
)
# include ディレクトリ.
set(INC_DIRS
  ${SRC_DIR}
  ${CMAKE_CURRENT_SOURCE_DIR}
)
# コンパイラ・オプション.
set(OPTS )
# ライブラリ・ディレクトリ.
set(LIB_DIRS )
# ライブラリ・ファイル.
set(LIBS )

# ツールチェイン名.
get_filename_component(TOOLCHAIN_NAME "${CMAKE_TOOLCHAIN_FILE}" NAME_WE)
string(REPLACE "-toolchain" "" TOOLCHAIN_NAME "${TOOLCHAIN_NAME}")

# コンパイラ・OS環境別の設定.
if(APPLE OR TOOLCHAIN_NAME MATCHES "mac" OR TOOLCHAIN_NAME MATCHES "linux")
  # mac linux(unix) では ncurses を使う.
  list(APPEND LIBS "ncurses")
elseif(TOOLCHAIN_NAME MATCHES "win" OR TOOLCHAIN_NAME MATCHES "dos")
  # Win / Dos なら PDCurses を使うための設定.
  list(APPEND LIB_DIRS "${THIRDPARTY_DIR}/lib/${TOOLCHAIN_NAME}")
  list(APPEND INC_DIRS "${THIRDPARTY_DIR}/include")
  list(APPEND LIBS "pdcurses")
  list(APPEND OPTS "-DUSE_PDCURSES")

  # VS で開いた時 hello プロジェクトがカレントになるようにする指定.
  if(TOOLCHAIN_NAME MATCHES "vc")
    set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
  endif()
endif()

# cmake:実行ファイル生成
add_executable(${PROJECT_NAME}
  ${SRCS}
  ${TOOLCHAIN_ADD_SRCS}
)
# cmake:インストール先を設定.
install(TARGETS ${PROJECT_NAME}
  RUNTIME DESTINATION "${CMAKE_SOURCE_DIR}/bin/${TOOLCHAIN_NAME}"
)
# cmake:コンパイル・オプション設定.
target_compile_options(${PROJECT_NAME} PRIVATE
  ${TOOLCHAIN_ADD_OPTS}
  ${OPTS}
)
# cmake: include ディレクトリ設定.
target_include_directories(${PROJECT_NAME} PRIVATE
  ${TOOLCHAIN_ADD_INCLUDE_DIRS}
  ${INC_DIRS}
)
# cmake: ライブラリ・ディレクトリ設定.
target_link_directories(${PROJECT_NAME} PRIVATE
  ${LIB_DIRS}
  ${TOOLCHAIN_ADD_LINK_DIRS}
)
# cmake: ライブラリ設定.
target_link_libraries(${PROJECT_NAME} PRIVATE
  ${LIBS}
  ${TOOLCHAIN_ADD_LIBS}
)

TOOLCHAIN_NAME については、各ツールチェイン・ファイル側で設定するのも考えたが、名前変えたファイル・コピーのみでコンパイラ個別バージョン対応ができることもあり、ファイル名からの取得にした。

mac は CMAKE_TOOLCHAIN_FILE を使わない場合もあるため APPLE でも判定。

ncurses の場合は OS 任せでほぼ素通りだけど、PDCurses の場合は USE_PDCURSES マクロを設定したり各種パス・ライブラリを設定したりする必要あり。

VERSION 3.24 を指定しているのは、Watcom c/c++ 対応がこのバージョン以降の仕組みに依存しているため。

project(hello C) で C を付けているのは、c++ ビルド環境チェックを発生させないため。今回のサンプルのコンパイラは全て c/c++ 両対応だが、他のcコンパイラのみの環境を試したときに c++ チェックにひっかかった。

個別のツールチェイン・ファイルの設定

ツールチェイン・ファイルとしては以下を用意。

名は、コンパイラや環境の判別に使うので、それを意味する文字列を含むようにしている。-toolchain.cmake を外した名がツールチェイン名。

別プロジェクトでの設定を元にしていることもあり、今回のサンプルでは必要のない設定も含まれているが、使い回す類の設定なのでそのままにしている。

linux

  • linux-toolchain.cmake

  • ホスト: linux(unix)

  • gcc を使用

  • -G は無指定(あるいは -G "Unix Makefiles" 等)

  • 内容は、文字エンコードを UTF-8 に、ワイド文字を UTF-32LE に設定
    Little Endian 環境前提。もし Big Endian 環境が必要なら別ツールチャイン・ファイルを用意する

    add_compile_options(-finput-charset=utf-8 -fexec-charset=utf-8 -fwide-exec-charset=utf-32LE)

    utf-8 設定しなくても問題ないかもしれないが、wchar_t は設定していないと使えないことがあった
    ※ utf-32に 'LE' 'BE'を付で指定ないと wcstombs, mbstowcs で BOM が付くワナ

  • たぶん他の unix 系 gcc / clang でも使えるつもり
    が、unix系の mac でビルド失敗したりするので...
    (文字エンコード指定が気に入らない模様)

mac

  • mac-toolchain.cmake
  • ホスト: mac (64bit arm intel)
  • xcode、apple-clang を使用
  • mac-toolchain.cmake は実質何も設定せず
    ※ homebrew環境のarm/intelの違いを対応しかけだが現状無しでもビルドできてる…
  • -G "Xcode" を指定しても CMAKE_TOOLCHAIN_FILE を設定すると、makefile 生成になってしまう,
    ので、bld.sh では、mac はツールチェイン・ファイルを使わずに
    cmake -G "Xcode" -B bld/mac .
    
    で生成。(CMakeLists.txt 側は APPLE で判定)

mingw

djgpp

  • djgpp-dos32-toolchain.cmake
  • ホスト: win (mingw32)
  • -G "MinGW Makefiles" 指定が必要
  • djgpp は こちら で使った MinGW32 でのクロスコンパイラ環境を使用
  • その頁で書いたツールチェイン・ファイルとほぼ同じ
    ターゲット環境指定、コンパイラ指定、ライブラリ拡張子、リンク方法、実行ファイル拡張子 等を設定.
    #ターゲット環境の設定.
    set(CMAKE_SYSTEM_NAME Generic)  
    set(CMAKE_SYSTEM_PROCESSOR x86)  
    #コンパイラ設定.
    set(CMAKE_C_COMPILER i586-pc-msdosdjgpp-gcc)  
    set(CMAKE_CXX_COMPILER i586-pc-msdosdjgpp-g++)  
    #c/c++コンパイラ・オプション(watcomに合わせて__FLAT__を追加)
    set(CMAKE_C_FLAGS "-D__FLAT__")  
    set(CMAKE_CXX_FLAGS "-D__FLAT__")  
    #ライブラリ設定(.a で staticライブラリを使う)
    set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")  
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")  
    set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)  
    #クロスコンパイラにつきc/c++の動作チェックをスキップ.
    set(CMAKE_C_COMPILER_WORKS TRUE)  
    set(CMAKE_CXX_COMPILER_WORKS TRUE)  
    #実行ファイルの拡張子を設定.
    set(CMAKE_EXECUTABLE_SUFFIX ".exe")  
    set(CMAKE_EXECUTABLE_SUFFIX_ASM ".exe")  
    set(CMAKE_EXECUTABLE_SUFFIX_C ".exe")  
    set(CMAKE_EXECUTABLE_SUFFIX_CXX ".exe")  
    
    ※ 文字エンコードは後で考える

watcom

  • watcom-win32-toolchain.cmake
    watcom-dos32-toolchain.cmake
    watcom-dos16-s-toolchain.cmake

  • ホスト: win

  • -G "Watcom WMake" 指定が必要

  • watcom1.9 を使用(watcom2.0betaも可)

  • 基本的に こちら で書いたtoolchainファイルの延長
    CMAKE_TOOLCHAIN_FILE を使うとコンパイラ指定等がクリアされるようなので、win用でもコンパイラ&ターゲット環境の設定が必要

    set(CMAKE_SYSTEM_NAME "Windows")
    set(CMAKE_SYSTEM_PROCESSOR "X86")
    set(CMAKE_C_COMPILER "wcl386")
    set(CMAKE_CXX_COMPILER "wcl386")
    set(CMAKE_WATCOM_RUNTIME_LIBRARY "MultiThreaded")

  • 16bit DOS については small モデルのみ用意。必要に応じて増やせば、で

    set(CMAKE_SYSTEM_NAME "dos")
    set(CMAKE_SYSTEM_PROCESSOR "I86")
    set(CMAKE_C_COMPILER "wcl")
    set(CMAKE_CXX_COMPILER "wcl")
    set(CMAKE_WATCOM_RUNTIME_LIBRARY "SingleThreaded")
    set(CMAKE_C_FLAGS "-ms -k4000 -D__DOS__ ${CMAKE_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "-ms -k4000 -D__DOS__ ${CMAKE_CXX_FLAGS}")

    ※ dos16-s については、アプリ固有の設定で、スタックサイズを追加している

vc

  • vc-win64-toolchain.cmake
    vc-win64-md-toolchain.cmake
    vc-win32-toolchain.cmake
    vc-win32-md-toolchain.cmake
    vc-winarm64-toolchain.cmake ※実行未確認
    vc-winarm-toolchain.cmake ※実行未確認

  • ホスト: win

  • -G "..." は sln が使えるバージョンの場合はそのバージョンの指定文字列を、古い VC の場合は "NMake Makefiles" を指定
    ※ cmake はバージョンが上がると古いVC向けの指定を削除することがあり、cmake v3.30 では vc12.0 以降でないと sln 生成できず

  • PDCurses のビルドは vc9 以降が ok(vc8 はエラー)
    arm64 は vc14.2,14.3でビルドのみ確認
    arm は vs14.0,14.2,14.3でビルドのみ確認 (vc14.1はツール追加が必要なのかも?)

  • -md 付は /MD(d) オプション指定で dll版Cランタイムライブラリを使う用

  • vc 用は設定多めなので、共通ファイル vc-incl.cmake を用意、vc-*-toolchain.cmake はそれを include して部分的に値を再設定する形にした

    vc-win32-md-toolchain.cmake
    >#vc設定共通ファイル読み込み.
    >include(${CMAKE_CURRENT_LIST_DIR}/vc-incl.cmake)
    >#ランタイムDLL版で CONFIGみて Debug の有無設定.
    >set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
    
    vc-incl.cmake
    #cl.exe を使う準備.
    find_program(CL_EXE cl.exe)
    if(NOT CL_EXE)
        message(FATAL_ERROR "cl.exe not found. Please ensure that MSVC is installed and cl.exe is in the PATH.")
    endif()
    #cl.exe 実行.
    execute_process(
        COMMAND "${CL_EXE}" /?
        RESULT_VARIABLE CL_RESULT
        OUTPUT_VARIABLE CL_OUTPUT
        ERROR_VARIABLE CL_ERROR
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_STRIP_TRAILING_WHITESPACE
    )
    
    #出力されたテキストから VCのバージョン番号(+アーキテクチャ)を取得.
    set(CL_FULL_OUTPUT "${CL_OUTPUT}\n${CL_ERROR}")
    string(REGEX MATCH "Version ([0-9]+\\.[0-9]+\\.[0-9]+).*for[ ]+([A-Za-z0-9]+)" CL_VERSION_MATCH "${CL_FULL_OUTPUT}")
    if(NOT CL_VERSION_MATCH)
        message(FATAL_ERROR "Failed to retrieve cl.exe version information. Output:\n${CL_FULL_OUTPUT}")
    endif()
    set(TOOLCHAIN_MSVC_VERSION "${CMAKE_MATCH_1}" CACHE STRING "MSVC Compiler Version" FORCE)
    set(TOOLCHAIN_TARGET_ARCH "${CMAKE_MATCH_2}" CACHE STRING "Toolchain Target arch" FORCE)
    
    #UTF8対応.
    if(TOOLCHAIN_MSVC_VERSION VERSION_GREATER_EQUAL "19.0.24215.1")
      add_compile_options(/utf-8)
    endif()
    
    #c++ 標準に寄せるオプション設定.
    add_compile_options(/Zc:wchar_t /Zc:forScope)
    if(TOOLCHAIN_MSVC_VERSION VERSION_GREATER_EQUAL "19.0.24215.1")
      add_compile_options(/Zc:rvalueCast)
    endif()
    if(TOOLCHAIN_MSVC_VERSION VERSION_GREATER_EQUAL "19.14.26428.1")
      add_compile_options(/Zc:__cplusplus)
    endif()
    
    #ビルドに必要なwin32ライブラリをCMakeLists.txt側へ通達する準備.
    set(TOOLCHAIN_ADD_LIBS "kernel32;user32;shell32;advapi32" CACHE STRING "Default Windows libraries")
    
  • コンパイラ・バージョン別にオプションを設定したいため、execute_process で cl.exe を実行して、出力される文字列からバージョン番号を拾って無理やり対処
    ※ project 設定後に有効の CMAKE_C_COMPILER_VERSION が使えないため

  • UTF-8 使用前提で、可能なら -utf-8 を設定

  • MS方言から c++ 標準仕様にする類のオプションを設定

  • 今回のサンプルの PDCurses に必要な win32 ライブラリを TOOLCHAIN_ADD_LIBS に設定 (アプリごとに変動)

  • vc のバージョン別に実行ファイルを作りたい場合、
    vc-win64-toolchain.cmake をコピーして vc140-win64-toolchain.cmake
    のようにバージョン値付 vc 名にすると、その変えたツールチェイン名でビルド可能
    (vc?? は install_pdcurses.bat で受け付けるコンパイラ名)

borland

たまたま残っていた bcc10.1(正式な名前わからず) の bcc32c で一応ビルドは通ってはいるのだけれど、よく普及していた bc5.5 のbcc32 や、新し目? のEmbarcadero C++ Builder とかだと、PDCursesのビルドでコンパイラ&リンク関係のオプション修正が必要そうで、boland対応、というには微妙な状態。一応書いておくと、

  • borland-win32-toolchain.cmake
  • ホスト: win
  • -G "Borland Makefiles" 指定が必要
  • CMAKE_TOOLCHAIN_FILE を使うとコンパイラ指定等がクリアされるようなので、コンパイラ&ターゲット環境を設定

    set(CMAKE_SYSTEM_NAME "Windows")
    set(CMAKE_SYSTEM_PROCESSOR "X86")
    set(CMAKE_C_COMPILER "bcc32c")
    set(CMAKE_CXX_COMPILER "bcc32c")

サンプル・ゲーム

(2025-01-26 追記)
hello を動かすだけでは味気ないので、別頁 にて、ミニゲームに置き換えてみた。

おわりに

既知のコンパイラまでツールチェイン・ファイルにするというのは、折角 cmake が用意した設定を御破算していて、cmake の使い方としては少々イレギュラーな気はする。

vc のツールチェイン・ファイルで、CMAKE_C_COMPILER_VERSION が使えないため execute_process で cl.exe を実行してバージョン番号を取得するのは本末転倒感あり。

が、未対応コンパイラを含めて一様に対応するなら、これがベターな気もしていて。

ただ、cmake推奨から外れた外部ライブラリのビルド(install_pdcurses.bat)とか、 [ツールチェイン名]-toolchain.cmake のようなファイル名ルールとか、TOOLCHAIN_??? のような専用変数とか、 bld スクリプトとか、だいぶ俺俺ビルドシステム的になってしまった。

人様の参考としては微妙だけれど、まあ、自分で使うための雛形のメモです。

2025-01-19 初版
2025-01-23 mac 指定での不具合修正、CMakeLists.txt 説明順番等変更

Discussion