C++20 以 Bazel & Clang 開始

C++20 如何以 Bazel & Clang 進(jìn)行構(gòu)建呢弯院?

本文將介紹:

  • Bazel 構(gòu)建系統(tǒng)的安裝
  • LLVM 編譯系統(tǒng)的安裝
    • Clang is an "LLVM native" C/C++/Objective-C compiler
  • Bazel Clang 工具鏈的配置
  • C++20 庫與應(yīng)用的構(gòu)建

本文示例可見: https://github.com/ikuokuo/start-cpp20

本文是于 Ubuntu 20 上進(jìn)行的實踐历葛,Windows 可以用 WSL 準(zhǔn)備環(huán)境。

安裝 Bazel韭赘,以二進(jìn)制方式

Bazelisk 是安裝 Bazel 的推薦方式筒捺,我們安裝它的二進(jìn)制發(fā)布即可:

cd ~
wget https://github.com/bazelbuild/bazelisk/releases/download/v1.12.0/bazelisk-linux-amd64 -O bazelisk-1.12.0-linux-amd64
chmod a+x bazelisk-*

sudo ln -s $(pwd)/bazelisk-1.12.0-linux-amd64 /usr/local/bin/bazel

touch WORKSPACE
# 國內(nèi)下載 Bazel 可能遇到如下問題柏腻,配置 .bazeliskrc 解決
# could not resolve the version 'latest' to an actual version number
#  https://github.com/bazelbuild/bazelisk/issues/220
cat <<-EOF > .bazeliskrc
BAZELISK_BASE_URL=https://github.com/bazelbuild/bazel/releases/download
USE_BAZEL_VERSION=5.2.0
EOF

bazel version

更多方式,可見官方文檔系吭。進(jìn)一步五嫂,推薦安裝 buildtools,下載后軟鏈一下:

sudo ln -s $(pwd)/buildifier-5.1.0-linux-amd64 /usr/local/bin/buildifier
sudo ln -s $(pwd)/buildozer-5.1.0-linux-amd64 /usr/local/bin/buildozer

Bazel 如何構(gòu)建 C++ 項目肯尺,可見我的 Start Bazel 筆記沃缘。

安裝 LLVM,以源碼方式

Clang 有關(guān) std::fromat 文本格式化的特性则吟,默認(rèn)未開啟:

The paper is implemented but still marked as an incomplete feature (the feature-test macro is not set and the libary is only available when built with LIBCXX_ENABLE_INCOMPLETE_FEATURES). Not yet implemented LWG-issues will cause API and ABI breakage.

C++20 特性槐臀,編譯器支持情況:

因此,這里以源碼方式安裝 LLVM氓仲,需要構(gòu)建 Clang & libc++:

git clone -b llvmorg-14.0.6 --depth 1 https://github.com/llvm/llvm-project.git

cd llvm-project
mkdir _build
cd _build

# llvm install path, such as /usr/local/llvm
LLVM_PREFIX=$HOME/Apps/llvm-14.0.6

cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$LLVM_PREFIX \
-DLLVM_ENABLE_PROJECTS=clang \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
-DLIBCXX_ENABLE_INCOMPLETE_FEATURES=ON \
../llvm

make -j`nproc`
make install

sudo ln -s $LLVM_PREFIX /usr/local/llvm

cat <<-EOF >> ~/.bashrc
# llvm
export LLVM_HOME=/usr/local/llvm
export PATH=\$LLVM_HOME/bin:\$PATH
export LD_LIBRARY_PATH=\$LLVM_HOME/lib/x86_64-unknown-linux-gnu:\$LD_LIBRARY_PATH
EOF

llvm-config --version
clang --version

LLVM_PREFIX 安裝路徑自己決定峰档。最后,編譯測試:

cat <<-EOF > hello.cc
#include <format>
#include <iostream>

int main() {
  std::string message = std::format("The answer is {}.", 42);
  std::cout << message << std::endl;
}
EOF

clang++ -std=c++20 -stdlib=libc++ hello.cc -o hello

./hello

安裝 LLVM寨昙,以二進(jìn)制方式

可省略該節(jié)讥巡。本文實踐未用此方式,因為想開啟更多 C++20 特性舔哪。這里僅作記錄欢顷,有需要可參考。

方式 1. 安裝二進(jìn)制發(fā)布

cd ~
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz
tar -xf clang+llvm-*.tar.xz

sudo ln -s $(pwd)/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04 /usr/local/llvm

cat <<-EOF >> ~/.bashrc
# llvm
export LLVM_HOME=/usr/local/llvm
export PATH=\$LLVM_HOME/bin:\$PATH
EOF

llvm-config --version
clang --version

方式 2. 用 apt 進(jìn)行安裝: https://apt.llvm.org/

方式 3. 用已配好的工具鏈: LLVM toolchain for Bazel

配置 Clang 工具鏈

本文依照 Bazel Tutorial: Configure C++ Toolchains 步驟配置的 Clang 工具鏈捉蚤,最后項目根目錄會多如下文件:

WORKSPACE 表示 Bazel 工作區(qū)抬驴,內(nèi)容空炼七。

.bazelrc 允許 --config=clang_config 啟用 Clang 工具鏈:

# Use our custom-configured c++ toolchain.
build:clang_config --crosstool_top=//toolchain:clang_suite

# Use --cpu as a differentiator.
build:clang_config --cpu=linux_x86_64

# Use the default Bazel C++ toolchain to build the tools used during the build.
build:clang_config --host_crosstool_top=@bazel_tools//tools/cpp:toolchain

toolchain/BUILD 配置 Clang 工具鏈信息:

load(":cc_toolchain_config.bzl", "cc_toolchain_config")

package(default_visibility = ["http://visibility:public"])

#filegroup(name = "clang_suite")

cc_toolchain_suite(
    name = "clang_suite",
    toolchains = {
        "linux_x86_64": ":linux_x86_64_toolchain",
    },
)

filegroup(name = "empty")

cc_toolchain(
    name = "linux_x86_64_toolchain",
    toolchain_identifier = "linux_x86_64-toolchain",
    toolchain_config = ":linux_x86_64_toolchain_config",
    all_files = ":empty",
    compiler_files = ":empty",
    dwp_files = ":empty",
    linker_files = ":empty",
    objcopy_files = ":empty",
    strip_files = ":empty",
    supports_param_files = 0,
)

#filegroup(name = "linux_x86_64_toolchain_config")

cc_toolchain_config(name = "linux_x86_64_toolchain_config")

toolchain/cc_toolchain_config.bzl 配置 Clang 工具鏈規(guī)則:

# C++ Toolchain Configuration
#  https://bazel.build/docs/cc-toolchain-config-reference
#  https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
load(
    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
    "feature",
    "flag_group",
    "flag_set",
    "tool_path",
)

all_compile_actions = [
    ACTION_NAMES.c_compile,
    ACTION_NAMES.cpp_compile,
    ACTION_NAMES.linkstamp_compile,
    ACTION_NAMES.assemble,
    ACTION_NAMES.preprocess_assemble,
    ACTION_NAMES.cpp_header_parsing,
    ACTION_NAMES.cpp_module_compile,
    ACTION_NAMES.cpp_module_codegen,
]

all_link_actions = [
    ACTION_NAMES.cpp_link_executable,
    ACTION_NAMES.cpp_link_dynamic_library,
    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
]

def _impl(ctx):
    llvm_version = "14.0.6"
    llvm_prefix = "/home/john/Apps/llvm-{}".format(llvm_version)
    llvm_bindir = llvm_prefix + "/bin"

    tool_paths = [
        tool_path(
            name = "gcc",
            path = llvm_bindir + "/clang",
        ),
        tool_path(
            name = "ld",
            path = llvm_bindir + "/ld.lld",
        ),
        tool_path(
            name = "ar",
            path = llvm_bindir + "/llvm-ar",
        ),
        tool_path(
            name = "cpp",
            path = llvm_bindir + "/clang-cpp",
        ),
        tool_path(
            name = "gcov",
            path = llvm_bindir + "/llvm-cov",
        ),
        tool_path(
            name = "nm",
            path = llvm_bindir + "/llvm-nm",
        ),
        tool_path(
            name = "objdump",
            path = llvm_bindir + "/llvm-objdump",
        ),
        tool_path(
            name = "strip",
            path = llvm_bindir + "/llvm-strip",
        ),
    ]

    features = [
        feature(
            name = "default_compiler_flags",
            enabled = True,
            flag_sets = [
                flag_set(
                    actions = all_compile_actions,
                    flag_groups = ([
                        flag_group(
                            flags = [
                                "-O2", "-DNDEBUG",
                                "-Wall", "-Wextra", "-Wpedantic", "-fPIC",
                                "-std=c++20", "-stdlib=libc++",
                            ],
                        ),
                    ]),
                ),
            ],
        ),
        feature(
            name = "default_linker_flags",
            enabled = True,
            flag_sets = [
                flag_set(
                    actions = all_link_actions,
                    flag_groups = ([
                        flag_group(
                            flags = [
                                "-lc++", "-lc++abi",
                                "-lm", "-ldl", "-lpthread",
                            ],
                        ),
                    ]),
                ),
            ],
        ),
    ]

    return cc_common.create_cc_toolchain_config_info(
        ctx = ctx,
        features = features,
        cxx_builtin_include_directories = [
            llvm_prefix + "/lib/clang/{}/include".format(llvm_version),
            llvm_prefix + "/include/x86_64-unknown-linux-gnu/c++/v1",
            llvm_prefix + "/include/c++/v1",
            "/usr/local/include",
            "/usr/include/x86_64-linux-gnu",
            "/usr/include",
        ],
        toolchain_identifier = "local",
        host_system_name = "local",
        target_system_name = "local",
        target_cpu = "linux_x86_64",
        target_libc = "unknown",
        compiler = "clang",
        abi_version = "unknown",
        abi_libc_version = "unknown",
        tool_paths = tool_paths,
    )

cc_toolchain_config = rule(
    implementation = _impl,
    attrs = {},
    provides = [CcToolchainConfigInfo],
)

llvm_prefix 給到自己的 LLVM 安裝路徑。

構(gòu)建 C++20 庫與應(yīng)用

本文示例的 code/00/ 路徑下準(zhǔn)備了 C++20 的庫與應(yīng)用:

code/00/
├── BUILD
├── greet
│   ├── BUILD
│   ├── greet.cc
│   └── greet.h
└── main.cc

編寫 binary

main.cc:

#include <format>
#include <iostream>
#include <string>
#include <string_view>

#include "greet/greet.h"

template <typename... Args>
std::string dyna_print(std::string_view rt_fmt_str, Args&&... args) {
  return std::vformat(rt_fmt_str, std::make_format_args(args...));
}

int main() {
  std::cout << greet::hello("world") << std::endl;

  std::string fmt;
  for (int i{}; i != 3; ++i) {
    fmt += "{} ";  // constructs the formatting string
    std::cout << fmt << " : ";
    std::cout << dyna_print(fmt, "alpha", 'Z', 3.14, "unused");
    std::cout << '\n';
  }
}

BUILD:

load("@rules_cc//cc:defs.bzl", "cc_binary")

cc_binary(
    name = "main",
    srcs = ["main.cc"],
    deps = [
        "http://code/00/greet:greet",
    ],
)

編寫 library

greet.h:

#pragma once

#include <string>
#include <string_view>

namespace greet {

std::string hello(std::string_view who);

}  // namespace greet

greet.cc:

#include "greet.h"

#include <format>
#include <utility>

namespace greet {

std::string hello(std::string_view who) {
  return std::format("Hello {}!", std::move(who));
}

}  // namespace greet

BUILD:

load("@rules_cc//cc:defs.bzl", "cc_library")

package(default_visibility = ["http://visibility:public"])

cc_library(
    name = "greet",
    srcs = [
        "greet.cc",
    ],
    hdrs = [
        "greet.h",
    ],
)

Bazel 構(gòu)建

bazel build --config=clang_config //code/00:main

運(yùn)行測試

$ bazel-bin/code/00/main
Hello world!
{}  : alpha
{} {}  : alpha Z
{} {} {}  : alpha Z 3.14

查看依賴

sudo apt update && sudo apt install graphviz xdot -y
# view
xdot <(bazel query --notool_deps --noimplicit_deps "deps(//code/00:main)" --output graph)
# to svg
dot -Tsvg <(bazel query --notool_deps --noimplicit_deps "deps(//code/00:main)" --output graph) -o 00_main.svg

更多參考

GoCoding 個人實踐的經(jīng)驗分享布持,可關(guān)注公眾號豌拙!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市题暖,隨后出現(xiàn)的幾起案子按傅,更是在濱河造成了極大的恐慌,老刑警劉巖胧卤,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唯绍,死亡現(xiàn)場離奇詭異,居然都是意外死亡枝誊,警方通過查閱死者的電腦和手機(jī)况芒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叶撒,“玉大人绝骚,你說我怎么就攤上這事§艄唬” “怎么了压汪?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哪审。 經(jīng)常有香客問我蛾魄,道長,這世上最難降的妖魔是什么湿滓? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任滴须,我火速辦了婚禮,結(jié)果婚禮上叽奥,老公的妹妹穿的比我還像新娘扔水。我一直安慰自己,他們只是感情好朝氓,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布魔市。 她就那樣靜靜地躺著,像睡著了一般赵哲。 火紅的嫁衣襯著肌膚如雪待德。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天枫夺,我揣著相機(jī)與錄音将宪,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛较坛,可吹牛的內(nèi)容都是我干的印蔗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼丑勤,長吁一口氣:“原來是場噩夢啊……” “哼华嘹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起法竞,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤耙厚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后爪喘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颜曾,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡纠拔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年秉剑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稠诲。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡侦鹏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臀叙,到底是詐尸還是另有隱情略水,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布劝萤,位于F島的核電站渊涝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏床嫌。R本人自食惡果不足惜跨释,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厌处。 院中可真熱鬧鳖谈,春花似錦、人聲如沸阔涉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑰排。三九已至贯要,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椭住,已是汗流浹背崇渗。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人显押。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓扳肛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乘碑。 傳聞我的和親對象是個殘疾皇子挖息,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容