LLVM/Clangでaws-sdk-cppをビルドする

LLVM/Clangでaws-sdk-cppをビルドしたらいろいろつまずきました。
2021.03.12

こんにちは、CX事業本部のうらわです。

aws-sdk-cppのビルドにおいて、Apple Clangではエラーがなかったのですがbrew install llvmで入手したClang(以降、LLVM/Clangと表記します)では同じコマンドでもエラーがありビルドに失敗してしまいました。

本記事では失敗した点とビルドに成功した対応策をご紹介します。なお、失敗した点は本記事の執筆時点の内容ですので、今後のaws-sdk-cppのアップデートで改善・解消する可能性があります。

作業環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H15

# cmakeはbrewでインストールしておきます
$ cmake --version
cmake version 3.18.4

$ brew info llvm
llvm: stable 11.1.0 (bottled), HEAD [keg-only]
Next-gen compiler infrastructure
https://llvm.org/
/usr/local/Cellar/llvm/11.0.0 (7,910 files, 1.2GB)

aws-sdk-cppのビルドコマンド

以下のコマンドでaws-sdk-cppをクローンしてcmakeでDynamoDBのSDKのコードのみをビルドします。全てビルドするとかなり時間がかかります。

CMAKE_INSTALL_PREFIXを指定して任意のディレクトリにSDKをインストールします。

git clone https://github.com/aws/aws-sdk-cpp.git
cd aws-sdk-cpp
mkdir build
cd build
cmake .. -DBUILD_ONLY="dynamodb" \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_INSTALL_PREFIX=/path/to/sdk-install-dir
make
make install

LLVM/Clangを使用する設定

MacではCommand Line Tools for XcodeをインストールするとC/C++のデフォルトのコンパイラにApple Clangが設定されます。

$ clang++ --version
Apple clang version 12.0.0 (clang-1200.0.32.29)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

brew install llvmで入手したLLVM/Clangを使用するにはパスを通す必要があります。

$ export PATH="/usr/local/opt/llvm/bin:$PATH"
$ clang++ --version
clang version 11.0.0
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm/bin

ただし、パスを通しただけではaws-sdk-cppのビルド時は依然としてApple Clangが使用されます。

$ cmake .. -DBUILD_ONLY="dynamodb" \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_INSTALL_PREFIX=/path/to/sdk-install-dir

-- Found Git: /usr/local/bin/git (found version "2.30.2")
-- TARGET_ARCH not specified; inferring host OS to be platform compilation target
-- Building AWS libraries as shared objects
-- Building project version: 1.8.160
-- The C compiler identification is AppleClang 12.0.0.12000032
-- The CXX compiler identification is AppleClang 12.0.0.12000032
...

LLVM/Clangを使うためには環境変数CCCXXを以下のように設定しておきます。

$ export CC="clang"
$ export CXX="clang++"

$ cmake .. -DBUILD_ONLY="dynamodb" \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_INSTALL_PREFIX=~/install

-- Found Git: /usr/local/bin/git (found version "2.30.2")
-- TARGET_ARCH not specified; inferring host OS to be platform compilation target
-- Building AWS libraries as shared objects
-- Building project version: 1.8.160
-- The C compiler identification is Clang 11.0.0
-- The CXX compiler identification is Clang 11.0.0
...

これでApple ClangではなくLLVM/Clangでビルドする準備ができました。

aws-c-commonビルド時のエラー

cmakeコマンド実行時に以下のエラーでビルドに失敗しました。

/path/to/aws-sdk-cpp/build/.deps/build/src/AwsCCommon/source/posix/system_info.c:136:34: error: this old-style function definition is not preceded by a prototype [-Werror,-Wstrict-prototypes]
const char *s_get_executable_path() {
                                 ^
1 error generated.
make[5]: *** [CMakeFiles/aws-c-common.dir/source/posix/system_info.c.o] Error 1
make[4]: *** [CMakeFiles/aws-c-common.dir/all] Error 2
make[3]: *** [all] Error 2
make[2]: *** [build/src/AwsCCommon-stamp/AwsCCommon-build] Error 2
make[1]: *** [CMakeFiles/AwsCCommon.dir/all] Error 2
make: *** [all] Error 2
CMake Error at CMakeLists.txt:224 (message):
  Failed to build third-party libraries.


-- Configuring incomplete, errors occurred!

これはaws-c-commonリポジトリにissueがありました。issueを参考にダウンロードされたコードを直接修正することで解消しました。

/path/to/aws-sdk-cpp/build/.deps/build/src/AwsCCommon/source/posix/system_info.c

- const char *s_get_executable_path() {
+ static const char *s_get_executable_path() {
    static const char *s_exe = NULL;
    if (AWS_LIKELY(s_exe)) {
        return s_exe;
    }
    uint32_t len = sizeof(s_exe_path);
    if (!_NSGetExecutablePath(s_exe_path, &len)) {
        s_exe = s_exe_path;
    }
    return s_exe;
}

make時のエラー

makeコマンド実行時、コンパイラオプションに-Werrorが設定されているため警告がエラーとなりビルドに失敗しました。

[ 41%] Building CXX object testing-resources/CMakeFiles/testing-resources.dir/source/MemoryTesting.cpp.o
In file included from /path/to/aws-sdk-cpp/testing-resources/source/MemoryTesting.cpp:9:
/path/to/aws-sdk-cpp/testing-resources/include/aws/external/gtest.h:11886:8: error: definition of implicit copy constructor for 'ValueArray2<bool, bool>' is deprecated because it has a user-declared copy assignment operator [-Werror,-Wdeprecated-copy]
  void operator=(const ValueArray2& other);
       ^
/path/to/aws-sdk-cpp/testing-resources/include/aws/external/gtest.h:17082:10: note: in implicit copy constructor for 'testing::internal::ValueArray2<bool, bool>' first required here
  return internal::ValueArray2<T1, T2>(v1, v2);
         ^
/path/to/aws-sdk-cpp/testing-resources/include/aws/external/gtest.h:17949:10: note: in instantiation of function template specialization 'testing::Values<bool, bool>' requested here
  return Values(false, true);
         ^
1 error generated.
make[2]: *** [testing-resources/CMakeFiles/testing-resources.dir/source/MemoryTesting.cpp.o] Error 1
make[1]: *** [testing-resources/CMakeFiles/testing-resources.dir/all] Error 2
make: *** [all] Error 2

これは、コンパイラオプションを変更して回避します。aws-sdk-cpp/cmake/compiler_setting.cmakeのコンパイラオプションを設定している箇所で-Werrorをはずします。

macro(set_gcc_warnings)
-    list(APPEND AWS_COMPILER_WARNINGS "-Wall" "-Werror" "-pedantic" "-Wextra")
+    list(APPEND AWS_COMPILER_WARNINGS "-Wall" "-pedantic" "-Wextra")
    if(COMPILER_CLANG)
        if(PLATFORM_ANDROID)
            # when using clang with libc and API lower than 21 we need to include Android support headers and ignore the gnu-include-next warning.
            if(ANDROID_STL MATCHES "libc" AND ANDROID_NATIVE_API_LEVEL_NUM LESS "21")
                # NDK lower than 12 doesn't support ignoring the gnu-include-next warning so we need to disable pedantic mode.
                if(NDK_RELEASE_NUMBER LESS "12000")
                    string(REGEX REPLACE "-pedantic" "" AWS_COMPILER_WARNINGS "${AWS_COMPILER_WARNINGS}")
                else()
                    list(APPEND AWS_COMPILER_WARNINGS "-Wno-gnu-include-next")
                endif()
            endif()
        endif()
    endif()
endmacro()

または、cmakeコマンド実行時にENABLE_TESTING=OFFを設定するとテストコードは無視されるため上記のようなエラーは発生しません。

$ cmake .. -DBUILD_ONLY="dynamodb" \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_INSTALL_PREFIX=/path/to/sdk-install-dir
  -DENABLE_TESTING=OFF

$ make

最善策がわからずこれで良いのか…という気がしています。コンパイラオプションの細かな調整はまだまだ勉強不足です。

DynamoDBを試す

無事ビルド・インストールが完了したら、あとは利用するだけです。今回はawslabs/aws-lambda-cppのコード例を参考に、DynamoDBテーブルの作成を試してみます。

create_table.cppCMakeLists.txtを作成します。

mkdir dynamodb_test
cd dynamodb_test
touch create_table.cpp
touch CMakeLists.txt

create_table.cpp

#include <aws/core/Aws.h>
#include <aws/core/utils/Outcome.h>
#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/model/AttributeDefinition.h>
#include <aws/dynamodb/model/CreateTableRequest.h>
#include <aws/dynamodb/model/KeySchemaElement.h>
#include <aws/dynamodb/model/ProvisionedThroughput.h>
#include <aws/dynamodb/model/ScalarAttributeType.h>
#include <iostream>


int main()
{
    Aws::SDKOptions options;

    Aws::InitAPI(options);
    {
        const Aws::String table("SampleTable");
        const Aws::String region("ap-northeast-1");

        Aws::Client::ClientConfiguration clientConfig;
        if (!region.empty())
            clientConfig.region = region;
        Aws::DynamoDB::DynamoDBClient dynamoClient(clientConfig);

        Aws::DynamoDB::Model::CreateTableRequest req;

        Aws::DynamoDB::Model::AttributeDefinition haskKey;
        haskKey.SetAttributeName("Name");
        haskKey.SetAttributeType(Aws::DynamoDB::Model::ScalarAttributeType::S);
        req.AddAttributeDefinitions(haskKey);

        Aws::DynamoDB::Model::KeySchemaElement keyscelt;
        keyscelt.WithAttributeName("Name").WithKeyType(Aws::DynamoDB::Model::KeyType::HASH);
        req.AddKeySchema(keyscelt);

        Aws::DynamoDB::Model::ProvisionedThroughput thruput;
        thruput.WithReadCapacityUnits(5).WithWriteCapacityUnits(5);
        req.SetProvisionedThroughput(thruput);

        req.SetTableName(table);

        const Aws::DynamoDB::Model::CreateTableOutcome& result = dynamoClient.CreateTable(req);
        if (result.IsSuccess())
        {
            std::cout << "Table " << result.GetResult().GetTableDescription().GetTableName() <<
                " created!" << std::endl;
        }
        else
        {
            std::cout << "Failed to create table: " << result.GetError().GetMessage();
        }
    }
    Aws::ShutdownAPI(options);
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(dynamodb-examples)

option(BUILD_SHARED_LIBS "Build shared libraries" ON)

find_package(AWSSDK REQUIRED COMPONENTS dynamodb)

add_executable(create_table create_table.cpp)

target_link_libraries(create_table ${AWSSDK_LINK_LIBRARIES})

ビルドします。CMAKE_INSTALL_PREFIXでさきほどビルドしたaws-sdk-cppのインストールディレクトリを指定します。

mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=/path/to/sdk-install-dir

make

実行してみます。事前にassume-roleしているため環境変数にクレデンシャルが設定されています。

$ ./create_table
Table SampleTable created!
* Closing connection 0

おわりに

Apple Clangでは一切エラーが出なかったのですがLLVM/Clangを使用するように変更したら色々つまずいてビルドするのに苦戦しました。

本記事の対応策は正攻法ではないかもしれませんが、LLVM/Clangを利用してaws-sdk-cppをビルドしたい場合の参考になれば幸いです。