CMakeの「Detecting CXX compiler ABI info - failed」を解決する

# はじめに
最近新たにセットアップしたノートパソコン（Windows）の環境で、CMakeに関する見慣れないエラーに遭遇しました。
クリーンな状態に最新のツールをそのままインストールしてきただけの環境なので、容易に原因は絞り込めそうだと思い、調べつつまとめてみることにしました。

# 環境
cmake version 3.29.1
GNU Make 3.81

# 発生した問題
以下のようなソースコードがあり、これをCMakeでコンパイルしようとしました。
```main.cpp
#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "Hello,world!" << std::endl;
    return 0;
}
```

CMakeLists側もシンプルです。
````CMakeLists.txt
cmake_minimum_required(VERSION 3.25)
project(
    CppHello
    VERSION 0.0.1
    LANGUAGES CXX
)
set(SOURCES "main.cpp")
add_executable(
    CppHello
    ${SOURCES}
)
target_compile_features(CppHello PRIVATE cxx_std_17)
````

これを、以下のようにコンパイルしようとしました。
````bat
cmake -G "Unix Makefiles" .
make
````

しかし、cmakeコマンドの時点で以下のようなエラーが発生しました。
````
-- The CXX compiler identification is Clang 18.1.2 with GNU-like command-line
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - failed
-- Check for working CXX compiler: C:/Program Files/LLVM/bin/clang++.exe
-- Check for working CXX compiler: C:/Program Files/LLVM/bin/clang++.exe - broken
CMake Error at C:/Program Files/CMake/share/cmake-3.29/Modules/CMakeTestCXXCompiler.cmake:60 (message):
  The C++ compiler

    "C:/Program Files/LLVM/bin/clang++.exe"

  is not able to compile a simple test program.

  It fails with the following output:

    Change Dir: 'C:/Users/%username%/Work/Repository/CppHello/CMakeFiles/CMakeScratch/TryCompile-b5le36'

    Run Build Command(s): "C:/Program Files/CMake/bin/cmake.exe" -E env VERBOSE=1 C:/Users/%username%/_bin/make-3.81-bin/bin/make.exe -f Makefile cmTC_5f626/fast
    Exit code 0xc0000135
````

このログからわかるのは、`CMakeTestCXXCompiler.cmake` の60行目でエラーログが出されているということです。
````CMakeTestCXXCompiler.cmake
# This file is used by EnableLanguage in cmGlobalGenerator to
# determine that the selected C++ compiler can actually compile
# and link the most basic of programs.   If not, a fatal error
# is set and cmake stops processing commands and will not generate
# any makefiles or projects.
if(NOT CMAKE_CXX_COMPILER_WORKS)
  PrintTestCompilerStatus("CXX")
  __TestCompiler_setTryCompileTargetType()
  string(CONCAT __TestCompiler_testCXXCompilerSource
    "#ifndef __cplusplus\n"
    "# error \"The CMAKE_CXX_COMPILER is set to a C compiler\"\n"
    "#endif\n"
    "int main(){return 0;}\n")
  # Clear result from normal variable.
  unset(CMAKE_CXX_COMPILER_WORKS)
  # Puts test result in cache variable.
  try_compile(CMAKE_CXX_COMPILER_WORKS
    SOURCE_FROM_VAR testCXXCompiler.cxx __TestCompiler_testCXXCompilerSource
    OUTPUT_VARIABLE __CMAKE_CXX_COMPILER_OUTPUT)
  unset(__TestCompiler_testCXXCompilerSource)
  # Move result from cache to normal variable.
  set(CMAKE_CXX_COMPILER_WORKS ${CMAKE_CXX_COMPILER_WORKS})
  unset(CMAKE_CXX_COMPILER_WORKS CACHE)
  __TestCompiler_restoreTryCompileTargetType()
  if(NOT CMAKE_CXX_COMPILER_WORKS)
    PrintTestCompilerResult(CHECK_FAIL "broken")
    string(REPLACE "\n" "\n  " _output "${__CMAKE_CXX_COMPILER_OUTPUT}")
    message(FATAL_ERROR "The C++ compiler\n  \"${CMAKE_CXX_COMPILER}\"\n"
      "is not able to compile a simple test program.\nIt fails "
      "with the following output:\n  ${_output}\n\n"
      "CMake will not be able to correctly generate this project.")
  endif()
  PrintTestCompilerResult(CHECK_PASS "works")
endif()
````

見に行くと、`__CMAKE_CXX_COMPILER_OUTPUT` という値がエラーログの本体であり、
それが `try_compile` から得られたものであることがわかります。
周囲の実装も含めて読んでみると、
CMakeが現在使用しようとしているコンパイラが正しいものであるかどうかを検証するために適当なソースコードをコンパイルさせようとしているように見えます。
事実、cmake実行時のエラーログには Makefile を実行しようとしていたらしきものが残っています。
````
    Change Dir: 'C:/Users/%username%/Work/Repository/CppHello/CMakeFiles/CMakeScratch/TryCompile-b5le36'

    Run Build Command(s): "C:/Program Files/CMake/bin/cmake.exe" -E env VERBOSE=1 C:/Users/%username%/_bin/make-3.81-bin/bin/make.exe -f Makefile cmTC_5f626/fast
````

であるなら、ここでコンパイルされているファイルをCMakeを通さずに直でclang++からビルドすればなにかわかりそうです。
なのですが、実はこのCMakeScratchにはファイルが残らないようなのです。
恐らくコンパイルが通るか確認したあとすぐに消しているのだと思います。
ですが幸い、CMakeのオプションに使えそうなものを見つけました。
````
  --debug-trycompile           = Do not delete the try_compile build tree.
                                 Only useful on one try_compile at a time.
````

ということで、これで実行してみます。
````
cmake -G "Unix Makefiles" --debug-trycompile .
````

すると、以下二つのフォルダが生成されました。（残りました。）
* TryCompile-41pm3t
* TryCompile-h9hw6o

treeコマンドで視覚化したものを参考に載せておきます。
````
.
├── TryCompile-41pm3t
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── Makefile2
│   │   ├── pkgRedirects
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── CMakeLists.txt
│   ├── cmake_install.cmake
│   ├── cmTC_23947_loc
│   └── Makefile
└── TryCompile-h9hw6o
    ├── CMakeCache.txt
    ├── CMakeFiles
    │   ├── Makefile2
    │   ├── pkgRedirects
    │   ├── progress.marks
    │   └── TargetDirectories.txt
    ├── CMakeLists.txt
    ├── cmake_install.cmake
    ├── cmTC_86786_loc
    ├── Makefile
    └── testCXXCompiler.cxx
````

遡ってログを見てもらえるとわかるのですが、cmakeは上記フォルダをcmakeの -E オプションを通じてビルドしているように見えます。
ですが、Makefileが置かれていることですし、まずは普通にmakeしてみました。
````
cd TryCompile-41pm3t
make
````

すると、こんなエラーが出てきました。
※ちなみにもう片方のディレクトリでも同様でした
![スクリーンショット 2024-04-07 173610.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263901/4c84ce2b-8f9c-af5d-5303-e5d97e05feb5.png)

一応cmake -Eでも実行してみたところ、同じ終了コードで失敗しました。
````
Exit code 0xc0000135
````

# 解決方法
メッセージにある通り、libintl3.dllを入手してmakeが見えるところに置けば解決しそうです。
そもそも私がどうやってmake(GnuWin32.Make)を入手したかというと、公式ページからバイナリ入手してパスを通しただけです。
https://gnuwin32.sourceforge.net/packages/make.htm

少し前までは
````bat
winget install GnuWin32.Make
````
でインストールできていたのですが、出来なくなっていたので自分でバイナリを置くことにしていました。

そしてこちらのサイトは `libintl` も公開しています。
https://gnuwin32.sourceforge.net/packages/libintl.htm

というか、そもそも…
makeのインストールページの最下部にRequirementsとしてlibintl,libiconvも記載されていたのです。
（私はmakeの実行バイナリだけを置いていたので実行できなかった）
恐らくwingetからインストールしたときは付随するものも一緒に入れてくれていたため、今までこれが発生しなかったのだと思います。

※makeのページからリンクされているlibiconvだけ正しいリンクになっていなかったぽいので、私が見つけたものを張っておきます。
https://gnuwin32.sourceforge.net/packages/libiconv.htm

ということで、
* libiconv2.dll
* libintl3.dll
* make.exe
を同じディレクトリに置くことでエラーは発生しなくなり、問題は解決しました。