C++ 工具鍊設定

回報問題 查看原始碼

總覽

為了以正確的選項叫用編譯器,Bazel 需要瞭解編譯器內部 (例如加入目錄和重要標記) 的相關資訊。換句話說,Bazel 需要簡化的編譯器模型才能瞭解其運作情形。

Bazel 必須知道下列事項:

  • 編譯器是否支援精簡格式、模組、動態連結或 PIC (位置獨立程式碼)。
  • 必要工具的路徑,例如 gcc、ld、ar、objcopy 等。
  • 內建系統包含目錄。Bazel 需要這些憑證才能驗證來源檔案中包含的所有標頭,是否都已在 BUILD 檔案中正確宣告。
  • 預設 sysroot。
  • 要用於編譯、連結、封存的標記。
  • 系統支援的編譯模式 (opt、dbg、Fastbuild) 要使用的標記。
  • 請製作編譯器特別需要的變數。

如果編譯器支援多個架構,Bazel 就必須分別設定這些架構。

CcToolchainConfigInfo 是可以提供設定 Bazel C++ 規則行為所需的精細程度的供應商。根據預設,Bazel 會自動為您的建構作業設定 CcToolchainConfigInfo,但您可以選擇手動設定。為此,您需要提供 CcToolchainConfigInfo 的 Starlark 規則,並將 cc_toolchaintoolchain_config 屬性指向您的規則。您可以呼叫 cc_common.create_cc_toolchain_config_info() 來建立 CcToolchainConfigInfo。您可以在 @rules_cc//cc:cc_toolchain_config_lib.bzl 中找到程序所需的所有結構體的 Starlark 建構函式。

C++ 目標進入分析階段時,Bazel 會根據 BUILD 檔案選取適當的 cc_toolchain 目標,然後從 cc_toolchain.toolchain_config 屬性中指定的目標取得 CcToolchainConfigInfo 提供者。cc_toolchain 目標會透過 CcToolchainProvider 將這項資訊傳遞至 C++ 目標。

舉例來說,根據 cc_binarycc_library 等規則將編譯或連結動作例項化後,就需要下列資訊:

  • 要使用的編譯器或連接器
  • 編譯器/連結器的指令列旗標
  • 透過 --copt/--linkopt 選項傳遞的設定旗標
  • 環境變數
  • 執行動作時沙箱所需的構件

以上所有資訊 (沙箱中所需的構件均是在 cc_toolchain 指向的 Starlark 目標中指定)。

要傳遞至沙箱的構件會在 cc_toolchain 目標中宣告。舉例來說,利用 cc_toolchain.linker_files 屬性,您就可以指定要傳送至沙箱的連結器二進位檔和工具鍊程式庫。

工具鍊選項

工具鍊選取邏輯的運作方式如下:

  1. 使用者在 BUILD 檔案中指定 cc_toolchain_suite 目標,並使用 --crosstool_top 選項將 Bazel 指向目標。

  2. cc_toolchain_suite 目標會參照多個工具鍊。--cpu--compiler 標記的值會決定已選取哪些工具鍊 (取決於僅根據 --cpu 旗標值,或根據共同的 --cpu | --compiler 值)。選取程序如下:

    • 如果指定 --compiler 選項,Bazel 會從 --cpu | --compilercc_toolchain_suite.toolchains 屬性中選取對應的項目。如果 Bazel 找不到對應的項目,就會擲回錯誤。

    • 如未指定 --compiler 選項,Bazel 只會從 --cpucc_toolchain_suite.toolchains 屬性中選取相應的項目。

    • 如未指定任何標記,Bazel 會檢查主機系統,並根據發現項目選取 --cpu 值。請參閱檢查機製程式碼

選取工具鍊後,Starlark 規則中的對應 featureaction_config 物件會控制建構作業的設定 (也就是稍後說明的項目)。這些訊息可在不修改 Bazel 二進位檔的情況下,在 Bazel 中實作完善的 C++ 功能。C++ 規則支援多項 Bazel 原始碼詳細說明的不重複動作。

功能

功能是一種實體,需要透過指令列旗標、動作、執行環境的限製或依附元件變更。例如允許 BUILD 檔案選取旗標設定 (例如 treat_warnings_as_errors),、與 C++ 規則互動,並在編譯中加入新的編譯動作和輸入內容 (例如 header_modulesthin_lto) 即可。

在理想情況下,CcToolchainConfigInfo 會包含功能清單,每個功能都含有一或多個標記群組,每個群組都會定義適用於特定 Bazel 動作的標記清單。

系統會依名稱指定功能,讓 Starlark 規則設定與 Bazel 版本完全分離。換句話說,只要 Bazel 版本不需要使用新功能, Bazel 版本就不會影響 CcToolchainConfigInfo 設定的行為。

功能可以透過下列其中一種方式啟用:

  • 地圖項目的 enabled 欄位設為 true
  • Bazel 或規則擁有者會明確啟用這項功能。
  • 使用者可以透過 --feature Bazel 選項或 features 規則屬性啟用該服務。

功能可能會有互通性,實際情況取決於指令列旗標、BUILD 檔案設定和其他變數。

功能關係

依附元件通常會直接透過 Bazel 管理,該 Bazel 只會強制執行要求,並管理與建構作業中定義的功能性質衝突。工具鍊規格允許更精細的限制,可直接在 Starlark 規則中使用,用於控管功能支援和擴展。包括:

限制 說明
requires = [
   feature_set (features = [
       'feature-name-1',
       'feature-name-2'
   ]),
]
功能層級:只有在指定的必要功能已啟用時,系統才會支援此功能。例如:某項功能僅在特定建構模式 (optdbgfastbuild) 中支援時。如果「requires」包含多個「feature_set」(滿足任何「feature_set」(啟用所有指定功能),則會支援該功能)。
implies = ['feature']

功能層級:此功能隱含指定的地圖項目。啟用功能時,也會以隱含方式啟用由該功能隱含的所有功能 (即以遞迴方式運作的功能)。

另外,也能從一組功能中排除常見的功能子集,例如消毒液的常見部分。無法停用隱含功能。

provides = ['feature']

功能層級:表示這項功能是多項互斥替代功能之一。舉例來說,所有消毒液都可以指定 provides = ["sanitizer"]

如果使用者同時要求兩個以上的互斥功能,這可以藉由列出替代項目來改善錯誤處理機制。

with_features = [
  with_feature_set(
    features = ['feature-1'],
    not_features = ['feature-2'],
  ),
]
旗標設定層級。功能可以指定多個具有多個標記的旗標集。指定 with_features 時,只有在至少一個 with_feature_set 已啟用,且指定 features 組合中的所有功能已啟用,且 not_features 組合中指定的所有功能都停用時,旗標組合才會展開為建構指令。如未指定 with_features,系統會無條件對每個指定的動作套用標記組合。

動作

動作可讓您在不假設動作執行方式的情況下,靈活地修改動作的執行情況。action_config 會指定叫用動作的工具二進位檔,feature 則指定設定 (旗標),可決定該工具在叫用動作時的行為。

功能會參照動作來指出影響到哪些 Bazel 動作,因為動作可以修改 Bazel 動作圖。CcToolchainConfigInfo 提供者包含具有與其關聯標記和工具的動作,例如 c++-compile。將標記與特徵建立關聯,即可指派給每個動作。

每個動作名稱都代表 Bazel 執行的一種動作類型,例如編譯或連結。但是,動作和 Bazel 動作類型之間是多對一的關係,其中 Bazel 動作類型是指執行動作 (例如 CppCompileAction) 的 Java 類別。具體來說,下表中的「編譯器動作」和「編譯器動作」是 CppCompileAction,而連結動作則為 CppLinkAction

組合器動作

動作 說明
preprocess-assemble 使用預先處理功能。通常用於 .S 檔案。
assemble 未經預先處理的組合。通常用於 .s 檔案。

編譯器動作

動作 說明
cc-flags-make-variable CC_FLAGS 傳播至 genrules。
c-compile 編譯為 C。
c++-compile 編譯為 C++。
c++-header-parsing 對標頭檔案執行編譯器的剖析器,確保標頭為獨立存在,否則會產生編譯錯誤。僅適用於支援模組的工具鍊。
動作 說明
c++-link-dynamic-library 連結包含所有依附元件的共用程式庫。
c++-link-nodeps-dynamic-library 連結只包含 cc_library 來源的共用程式庫。
c++-link-executable 連結最終可執行的程式庫。

AR 動作

AR 動作會透過 ar 將物件檔案組合成封存程式庫 (.a 檔案),並將部分語意編碼為名稱。

動作 說明
c++-link-static-library 建立靜態資料庫 (封存)。

LTO 動作

動作 說明
lto-backend ThinLTO 動作將位元碼編譯為原生物件。
lto-index 產生全域索引的 ThinLTO 動作。

使用 action_config

action_config 是一種 Starlark 結構體,用來描述 Bazel 動作,方法則是指定在動作和標記組合 (由功能定義) 期間叫用的工具 (二進位檔)。這些旗標會將限制套用至動作的執行作業。

action_config() 建構函式包含下列參數:

屬性 說明
action_name 這個動作對應的 Bazel 動作。Bazel 會使用這項屬性來查找每個動作的工具與執行需求。
tools 要叫用的執行檔。套用至動作的工具將是清單中的第一個工具,具有與功能設定相符的功能集。必須提供預設值。
flag_sets 套用至一組動作的標記清單。與地圖項目相同。
env_sets 套用至一組動作的環境限制條件清單。與地圖項目相同。

action_config 可能要求及隱含其他功能和 action_config,如前述功能關係所述。這種行為與功能類似。

最後兩個屬性會多餘的屬性對對應的對應屬性重複,並納入其中,因為某些 Bazel 動作會需要特定標記或環境變數,而目標是避免不必要的 action_configfeature 組合。一般而言,建議在多個 action_config 共用單一功能。

在同一個工具鍊中,您無法以相同的 action_name 定義多個 action_config。這可避免工具路徑產生混淆,並強制執行 action_config 背後的意圖,因為動作的屬性可在工具鍊的單一位置清楚描述。

使用工具建構函式

action_config 可以透過其 tools 參數指定一組工具。tool() 建構函式會採用下列參數:

欄位 說明
path 相關工具的路徑 (相對於目前位置)。
with_features 功能集清單,其中至少必須符合一項功能才能套用這項工具。

就指定的 action_config 而言,只有一個 tool 會將工具路徑和執行要求套用到 Bazel 動作。選取工具時,系統會疊代 action_config 上的 tools 屬性,直到找到與功能設定相符的 with_feature 組合為止 (詳情請參閱本頁前述的「功能關係」)。您應該使用對應至空白功能設定的預設工具做為工具清單的結尾。

應用實例

這些功能和動作可搭配使用,以多樣化的跨平台語意來實作 Bazel 動作。舉例來說,在 macOS 上產生偵錯符號時,必須在編譯動作中產生符號,然後在連結動作期間叫用特殊工具來建立壓縮的 dsym 封存,然後解壓縮該封存檔,產生應用程式套件和 Xcode 使用的 .plist 檔案。

使用 Bazel 時,可以改為按照下列方式實作這項程序,並將 unbundle-debuginfo 做為 Bazel 動作:

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

action_configs = [
    action_config (
        action_name = ACTION_NAMES.cpp_link_executable,
        tools = [
            tool(
                with_features = [
                    with_feature(features=["generate-debug-symbols"]),
                ],
                path = "toolchain/mac/ld-with-dsym-packaging",
            ),
            tool (path = "toolchain/mac/ld"),
        ],
    ),
]

features = [
    feature(
        name = "generate-debug-symbols",
        flag_sets = [
            flag_set (
                actions = [
                    ACTION_NAMES.c_compile,
                    ACTION_NAMES.cpp_compile
                ],
                flag_groups = [
                    flag_group(
                        flags = ["-g"],
                    ),
                ],
            )
        ],
        implies = ["unbundle-debuginfo"],
   ),
]

這項功能可以完全以不同方式實作,而且可在 Linux (使用 fission) 或 Windows 中產生 .pdb 檔案。舉例來說,以 fission 為基礎的偵錯符號產生實作方式可能如下所示:

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

action_configs = [
    action_config (
        name = ACTION_NAMES.cpp_compile,
        tools = [
            tool(
                path = "toolchain/bin/gcc",
            ),
        ],
    ),
]

features = [
    feature (
        name = "generate-debug-symbols",
        requires = [with_feature_set(features = ["dbg"])],
        flag_sets = [
            flag_set(
                actions = [ACTION_NAMES.cpp_compile],
                flag_groups = [
                    flag_group(
                        flags = ["-gsplit-dwarf"],
                    ),
                ],
            ),
            flag_set(
                actions = [ACTION_NAMES.cpp_link_executable],
                flag_groups = [
                    flag_group(
                        flags = ["-Wl", "--gdb-index"],
                    ),
                ],
            ),
      ],
    ),
]

檢舉群組

CcToolchainConfigInfo 可讓您將標記分為具有特定用途的群組。您可以使用旗標值內的預先定義變數來指定標記,編譯器會在將標記新增至建構指令時展開。例如:

flag_group (
    flags = ["%{output_execpath}"],
)

在此情況下,系統會將標記的內容替換為動作的輸出檔案路徑。

旗標群組會依照在清單中顯示的順序 (由上到下和從左到右) 展開至建構指令。

對於新增至建構指令時,需要以不同值重複的標記,標記群組可以疊代 list 類型的變數。例如,list 類型的變數 include_path

flag_group (
    iterate_over = "include_paths",
    flags = ["-I%{include_paths}"],
)

會展開為 include_paths 清單中每個路徑元素的 -I<path>。標記群組宣告內文中的所有標記 (或 flag_group) 都會展開為一個單位。例如:

flag_group (
    iterate_over = "include_paths",
    flags = ["-I", "%{include_paths}"],
)

會展開為 include_paths 清單中每個路徑元素的 -I <path>

變數可以重複多次。例如:

flag_group (
    iterate_over = "include_paths",
    flags = ["-iprefix=%{include_paths}", "-isystem=%{include_paths}"],
)

會展開為:

-iprefix=<inc0> -isystem=<inc0> -iprefix=<inc1> -isystem=<inc1>

變數可以對應至使用點標記法可存取的結構。舉例來說:

flag_group (
    flags = ["-l%{libraries_to_link.name}"],
)

結構可以是巢狀結構,也可以包含序列。為避免名稱衝突並明確顯示,您必須透過欄位指定完整路徑。舉例來說:

flag_group (
    iterate_over = "libraries_to_link",
    flag_groups = [
        flag_group (
            iterate_over = "libraries_to_link.shared_libraries",
            flags = ["-l%{libraries_to_link.shared_libraries.name}"],
        ),
    ],
)

條件式擴充

旗標群組支援根據特定變數或其欄位,使用 expand_if_availableexpand_if_not_availableexpand_if_trueexpand_if_falseexpand_if_equal 屬性進行條件式擴充。例如:

flag_group (
    iterate_over = "libraries_to_link",
    flag_groups = [
        flag_group (
            iterate_over = "libraries_to_link.shared_libraries",
            flag_groups = [
                flag_group (
                    expand_if_available = "libraries_to_link.shared_libraries.is_whole_archive",
                    flags = ["--whole_archive"],
                ),
                flag_group (
                    flags = ["-l%{libraries_to_link.shared_libraries.name}"],
                ),
                flag_group (
                    expand_if_available = "libraries_to_link.shared_libraries.is_whole_archive",
                    flags = ["--no_whole_archive"],
                ),
            ],
        ),
    ],
)

CcToolchainConfigInfo 參考資料

本節提供建構變數、功能,以及成功設定 C++ 規則所需的其他資訊。

CcToolchainConfigInfo 建構變數

以下是 CcToolchainConfigInfo 建構變數的參照。

變數 動作 說明
source_file compile 要編譯的來源檔案。
input_file 條紋 要去除的成果。
output_file compile 編譯輸出。
output_assembly_file compile 發送的組合檔案。僅適用於 compile 動作發出組合文字時,通常是使用 --save_temps 旗標時。內容與 output_file 相同。
output_preprocess_file compile 預先處理的輸出內容。僅適用於只預先處理來源檔案的編譯動作,通常在使用 --save_temps 標記時。內容與 output_file 相同。
includes compile 編譯器必須無條件納入已編譯原始碼中的檔案序列。
include_paths compile 編譯器搜尋使用 #include<foo.h>#include "foo.h" 包含的標頭的序列目錄。
quote_include_paths compile -iquote 的序列包括一個目錄,也就是編譯器會在其中搜尋使用 #include "foo.h" 包含的標頭的目錄。
system_include_paths compile -isystem 的序列包括一個目錄,也就是編譯器會在其中搜尋使用 #include <foo.h> 包含的標頭的目錄。
dependency_file compile 編譯器產生的 .d 依附元件檔案。
preprocessor_defines compile defines 的序列,例如 --DDEBUG
pic compile 將輸出內容編譯為獨立程式碼。
gcov_gcno_file compile gcov 涵蓋率檔案。
per_object_debug_info_file compile 個別物件的偵錯資訊 (.dwp) 檔案。
stripotps 條紋 stripopts」的序列。
legacy_compile_flags compile 舊版 CROSSTOOL 欄位的標記序列,例如 compiler_flagoptional_compiler_flagcxx_flagoptional_cxx_flag
user_compile_flags compile copt 規則屬性或 --copt--cxxopt--conlyopt 標記的標記序列。
unfiltered_compile_flags compile unfiltered_cxx_flag 舊版 CROSSTOOL 欄位或 unfiltered_compile_flags 功能的標記序列。這些不會遭到 nocopts 規則屬性篩選。
sysroot sysroot
runtime_library_search_directories link (連結) 連結器執行階段搜尋路徑中的項目 (通常使用 -rpath 旗標設定)。
library_search_directories link (連結) 連結器搜尋路徑中的項目 (通常使用 -L 旗標設定)。
libraries_to_link link (連結) 提供檔案以連結做為連結器叫用中的輸入內容的旗標。
def_file_path link (連結) 在 Windows 搭配 MSVC 使用的定義檔案位置。
linker_param_file link (連結) 由 bazel 建立的連結器參數檔案位置,用於超過指令列長度限制。
output_execpath link (連結) 連結器輸出內容的執行路徑。
generate_interface_library link (連結) "yes""no",視是否應產生介面程式庫而定。
interface_library_builder_path link (連結) 介面程式庫建構工具的路徑。
interface_library_input_path link (連結) 介面程式庫 ifso 建構工具的輸入內容。
interface_library_output_path link (連結) 使用 ifso 建構工具產生介面程式庫的路徑。
legacy_link_flags link (連結) 來自舊版 CROSSTOOL 欄位的連結器標記。
user_link_flags link (連結) 來自 --linkoptlinkopts 屬性的連結器標記。
linkstamp_paths link (連結) 提供連結戳記路徑的建構變數。
force_pic link (連結) 出現這個變數時,代表應產生 PIC/PIE 程式碼 (已傳送 Bazel 選項「--force_pic」)。
strip_debug_symbols link (連結) 出現這個變數時,代表應移除偵錯符號。
is_cc_test link (連結) 如果是 cc_test 連結動作,則為 false,否則傳回 false。
is_using_fission compile, link 出現這個變數時,表示已啟用 (依物件偵錯資訊) 已啟用。偵錯資訊位於 .dwo 檔案中,而非 .o 檔案,而編譯器和連接器必須知道這項資訊。
fdo_instrument_path compile, link 儲存 FDO 檢測設定檔的目錄路徑。
fdo_profile_path compile FDO 設定檔的路徑。
fdo_prefetch_hints_path compile 快取預先擷取設定檔的路徑。
cs_fdo_instrument_path compile, link 儲存情境敏感 FDO 檢測設定檔的目錄路徑。

知名功能

以下為各項功能及其啟用條件的參考資料。

功能 說明文件
opt | dbg | fastbuild 系統會根據編譯模式預設啟用。
static_linking_mode | dynamic_linking_mode 依連結模式預設為啟用。
per_object_debug_info 如果指定並啟用 supports_fission 功能,且在 --fission 標記中指定目前的編譯模式,則可啟用此功能。
supports_start_end_lib 如果啟用此設定 (且已設定 --start_end_lib 選項),Bazel 就不會連結至靜態資料庫,而是使用 --start-lib/--end-lib 連接器選項直接連結至物件。由於 Bazel 不必建構靜態資料庫,因此能加快建構速度。
supports_interface_shared_libraries 如果啟用 (且已設定 --interface_shared_objects 選項),Bazel 會將 linkstatic 設為 False (預設為 cc_test) 的目標連結到介面共用程式庫。這會加快漸進式重新連結的速度。
supports_dynamic_linker 啟用後,C++ 規則就會知道工具鍊可以產生共用程式庫。
static_link_cpp_runtimes 啟用後,Bazel 會在靜態連結模式下以靜態方式連結 C++ 執行階段,並在動態連結模式中動態連結。視連結模式而定,cc_toolchain.static_runtime_libcc_toolchain.dynamic_runtime_lib 屬性中指定的構件會新增至連結動作。
supports_pic 啟用後,工具鍊就會知道將 PIC 物件用於動態程式庫。每次需要 PIC 編譯時,都會出現 `pic` 變數。如果預設為未啟用,並傳遞「--force_pic」,則 Bazel 會要求「supports_pic」並驗證這項功能是否已啟用。如果缺少或無法啟用該功能,則無法使用「--force_pic」。
static_linking_mode | dynamic_linking_mode 依連結模式預設為啟用。
no_legacy_features 防止 Bazel 在有舊版功能存在時將其新增至 C++ 設定。詳情請參閱下方完整的功能清單。

舊版功能修補邏輯

為達成回溯相容性,Bazel 會將下列變更套用至工具鍊的功能:

  • legacy_compile_flags 功能移至工具鍊頂端
  • default_compile_flags 功能移至工具鍊頂端
  • dependency_file (如果沒有) 功能新增至工具鍊頂端
  • pic (如果沒有) 功能新增至工具鍊頂端
  • per_object_debug_info (如果沒有) 功能新增至工具鍊頂端
  • preprocessor_defines (如果沒有) 功能新增至工具鍊頂端
  • includes (如果沒有) 功能新增至工具鍊頂端
  • include_paths (如果沒有) 功能新增至工具鍊頂端
  • fdo_instrument (如果沒有) 功能新增至工具鍊頂端
  • fdo_optimize (如果沒有) 功能新增至工具鍊頂端
  • cs_fdo_instrument (如果沒有) 功能新增至工具鍊頂端
  • cs_fdo_optimize (如果沒有) 功能新增至工具鍊頂端
  • fdo_prefetch_hints (如果沒有) 功能新增至工具鍊頂端
  • autofdo (如果沒有) 功能新增至工具鍊頂端
  • build_interface_libraries (如果沒有) 功能新增至工具鍊頂端
  • dynamic_library_linker_tool (如果沒有) 功能新增至工具鍊頂端
  • shared_flag (如果沒有) 功能新增至工具鍊頂端
  • linkstamps (如果沒有) 功能新增至工具鍊頂端
  • output_execpath_flags (如果沒有) 功能新增至工具鍊頂端
  • runtime_library_search_directories (如果沒有) 功能新增至工具鍊頂端
  • library_search_directories (如果沒有) 功能新增至工具鍊頂端
  • archiver_flags (如果沒有) 功能新增至工具鍊頂端
  • libraries_to_link (如果沒有) 功能新增至工具鍊頂端
  • force_pic_flags (如果沒有) 功能新增至工具鍊頂端
  • user_link_flags (如果沒有) 功能新增至工具鍊頂端
  • legacy_link_flags (如果沒有) 功能新增至工具鍊頂端
  • static_libgcc (如果沒有) 功能新增至工具鍊頂端
  • fission_support (如果沒有) 功能新增至工具鍊頂端
  • strip_debug_symbols (如果沒有) 功能新增至工具鍊頂端
  • coverage (如果沒有) 功能新增至工具鍊頂端
  • llvm_coverage_map_format (如果沒有) 功能新增至工具鍊頂端
  • gcc_coverage_map_format (如果沒有) 功能新增至工具鍊頂端
  • fully_static_link (如果沒有) 功能新增至工具鍊底部
  • user_compile_flags (如果沒有) 功能新增至工具鍊底部
  • sysroot (如果沒有) 功能新增至工具鍊底部
  • unfiltered_compile_flags (如果沒有) 功能新增至工具鍊底部
  • linker_param_file (如果沒有) 功能新增至工具鍊底部
  • compiler_input_flags (如果沒有) 功能新增至工具鍊底部
  • compiler_output_flags (如果沒有) 功能新增至工具鍊底部

這是詳盡的功能清單。計劃在Starlark 中的 Crosstool 後即清除這些元件。對一些好奇的讀者而言,請參閱 CppActionConfigs 中的實作說明,如果是實際工作環境工具鍊,請考慮新增 no_legacy_features 來使工具鍊更獨立。