Bazel Tutorial: הגדרת +C +chains

המדריך הזה משתמש בתרחיש לדוגמה כדי לתאר את אופן ההגדרה של כלי C++ לפרויקט. הוא מבוסס על פרויקט לדוגמה C++ שיוצר מבנים ללא שגיאות באמצעות clang.

מה תלמדו

במדריך הזה לומדים איך:

  • הגדרה של סביבת ה-build
  • הגדרת מחזיק הכלים C++
  • יצירת כלל של Starlark שמספק תצורה נוספת עבור cc_toolchain כדי ש-Bazel תוכל לבנות את האפליקציה עם clang
  • כדי לאשר את התוצאה הצפויה, יש להפעיל את bazel build --config=clang_config //main:hello-world במחשב Linux
  • בניית האפליקציה C++

לפני שמתחילים

הגדרה של סביבת ה-build

המדריך הזה מבוסס על ההנחה שאתם משתמשים ב-Linux ובנו בהצלחה C++ אפליקציות והתקנתם את הכלים והספריות המתאימים. המדריך משתמש ב-clang version 9.0.1, שאותו אפשר להתקין במערכת.

מגדירים את סביבת ה-build באופן הבא:

  1. אם עדיין לא עשית זאת, יש להוריד ולהתקין את Bazel 0.23 ואילך.

  2. הורידו את הפרויקט לדוגמה C++ מ-GitHub והציבו אותו בספרייה ריקה במחשב המקומי שלכם.

  3. מוסיפים את היעד cc_binary הבא לקובץ main/BUILD:

    cc_binary(
        name = "hello-world",
        srcs = ["hello-world.cc"],
    )
    
  4. כדי לאפשר שימוש בסימון --config, יש ליצור קובץ .bazelrc בשורש הספרייה של סביבת העבודה עם התוכן הבא:

    # Use our custom-configured c++ toolchain.
    
    build:clang_config --crosstool_top=//toolchain:clang_suite
    
    # Use --cpu as a differentiator.
    
    build:clang_config --cpu=k8
    
    # 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
    

עבור רשומה build:{config_name} --flag=value, סימון שורת הפקודה --config={config_name} משויך לדגל המסוים הזה. אפשר לעיין במסמכי התיעוד של הסימונים שהיו בשימוש: crosstool_top, cpu ו- host_crosstool_top.

כשמגדירים את target עם bazel build --config=clang_config //main:hello-world, Bazel משתמשת ב-chail Tool שלך בהתאמה אישית מ- cc_toolchain_Suite //toolchain:clang_suite. בסוויטה עשויים להיות רשומים כלים שונים עבור מעבדים שונים, ולכן היא שונה מהדגל --cpu=k8.

מכיוון ש-Bazel משתמשת בכלים פנימיים רבים הכתובים ב-C++ במהלך ה-build, כגון תהליך-wrapper, כלי ברירת המחדל הקיים מראש של C++ מצוין עבור הפלטפורמה המארחת, כך שכלים אלו בנויים באמצעות 'כלי כלים' זה במקום זה שנוצר במדריך זה.

הגדרת מחזיק הכלים C++

כדי להגדיר את ארגז הכלים של C++ , בונים שוב ושוב את האפליקציה ומבטלים כל שגיאה בנפרד, כמתואר בהמשך.

  1. הרץ את ה-build עם הפקודה הבאה:

    bazel build --config=clang_config //main:hello-world
    

    מכיוון שציינת את --crosstool_top=//toolchain:clang_suite בקובץ .bazelrc, Bazel מציג את השגיאה הבאה:

    No such package `toolchain`: BUILD file not found on package path.
    

    בספרייה של סביבת העבודה, יש ליצור את הספרייה של toolchain עבור החבילה וקובץ BUILD ריק בתוך הספרייה של toolchain.

  2. הרץ שוב את ה-build. מכיוון שהחבילה toolchain עדיין לא מגדירה את היעד clang_suite, Bazel מציג את השגיאה הבאה:

    No such target '//toolchain:clang_suite': target 'clang_suite' not declared
    in package 'toolchain' defined by .../toolchain/BUILD
    

    בקובץ ה-toolchain/BUILD, יש להגדיר קבוצת קבצים ריקה:

    package(default_visibility = ["//visibility:public"])
    
    filegroup(name = "clang_suite")
    
  3. הרץ שוב את ה-build. Bazel מציג את השגיאה הבאה:

    '//toolchain:clang_suite' does not have mandatory providers: 'ToolchainInfo'
    

    בזל גילתה שהסימון של --crosstool_top מפנה לכלל שלא מספק את הספק הנדרש ToolchainInfo. במקרה כזה עליך להפנות את --crosstool_top לכלל שמספק ToolchainInfo – זהו הכלל cc_toolchain_suite. בקובץ toolchain/BUILD, מחליפים את קבוצת הקבצים הריקה בפריטים הבאים:

    cc_toolchain_suite(
        name = "clang_suite",
        toolchains = {
            "k8": ":k8_toolchain",
        },
    )
    

    המאפיין toolchains ממפה באופן אוטומטי את הערכים --cpu (ובנוסף --compiler כאשר הם מצוינים) ל-cc_toolchain. עדיין לא הגדרת יעדים של cc_toolchain ובזל יתלונן על כך בקרוב.

  4. הרץ שוב את ה-build. Bazel מציג את השגיאה הבאה:

    Rule '//toolchain:k8_toolchain' does not exist
    

    עכשיו צריך להגדיר cc_toolchain יעדים לכל ערך במאפיין cc_toolchain_suite.toolchains. יש להוסיף את הערכים הבאים לקובץ toolchain/BUILD:

    filegroup(name = "empty")
    
    cc_toolchain(
        name = "k8_toolchain",
        toolchain_identifier = "k8-toolchain",
        toolchain_config = ":k8_toolchain_config",
        all_files = ":empty",
        compiler_files = ":empty",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        supports_param_files = 0,
    )
    
  5. הרץ שוב את ה-build. Bazel מציג את השגיאה הבאה:

    Rule '//toolchain:k8_toolchain_config' does not exist
    

    בשלב הבא, יש להוסיף יעד ":k8_toolchain_config" לקובץ toolchain/BUILD:

    filegroup(name = "k8_toolchain_config")
    
  6. הרץ שוב את ה-build. Bazel מציג את השגיאה הבאה:

    '//toolchain:k8_toolchain_config' does not have mandatory providers:
    'CcToolchainConfigInfo'
    

    CcToolchainConfigInfo הוא ספק שבו אתם משתמשים כדי להגדיר את מחזיקי הכלים מסוג C++. כדי לתקן את השגיאה הזו, יש ליצור כלל של Starlark שמספק CcToolchainConfigInfo ל-Bazel על ידי יצירת קובץtoolchain/cc_toolchain_config.bzl עם התוכן הבא:

    def _impl(ctx):
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            toolchain_identifier = "k8-toolchain",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
        )
    
    cc_toolchain_config = rule(
        implementation = _impl,
        attrs = {},
        provides = [CcToolchainConfigInfo],
    )
    

    cc_common.create_cc_toolchain_config_info() הוא הספק הנדרש CcToolchainConfigInfo. כדי להשתמש בכלל cc_toolchain_config, יש להוסיף הצהרת עומס אל toolchains/BUILD:

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

    ומחליפים את קבוצת הקבצים "k8_toolchain_config" בהצהרה של cc_toolchain_config:

    cc_toolchain_config(name = "k8_toolchain_config")
    
  7. הרץ שוב את ה-build. Bazel מציג את השגיאה הבאה:

    .../BUILD:1:1: C++ compilation of rule '//:hello-world' failed (Exit 1)
    src/main/tools/linux-sandbox-pid1.cc:421:
    "execvp(toolchain/DUMMY_GCC_TOOL, 0x11f20e0)": No such file or directory
    Target //:hello-world failed to build`
    

    בשלב זה יש לבזל מספיק מידע כדי לנסות לבנות את הקוד, אבל היא עדיין לא יודעת באילו כלים להשתמש כדי להשלים את פעולות ה-build הנדרשות. עליך לשנות את יישום הכלל של Starlark כדי להורות ל-Bazel באילו כלים להשתמש. לשם כך, צריך להשתמש בבנאי Tool_path() מ- @bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl:

    # toolchain/cc_toolchain_config.bzl:
    # NEW
    load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "tool_path")
    
    def _impl(ctx):
        tool_paths = [ # NEW
            tool_path(
                name = "gcc",
                path = "/usr/bin/clang",
            ),
            tool_path(
                name = "ld",
                path = "/usr/bin/ld",
            ),
            tool_path(
                name = "ar",
                path = "/usr/bin/ar",
            ),
            tool_path(
                name = "cpp",
                path = "/bin/false",
            ),
            tool_path(
                name = "gcov",
                path = "/bin/false",
            ),
            tool_path(
                name = "nm",
                path = "/bin/false",
            ),
            tool_path(
                name = "objdump",
                path = "/bin/false",
            ),
            tool_path(
                name = "strip",
                path = "/bin/false",
            ),
        ]
        return cc_common.create_cc_toolchain_config_info(
            ctx = ctx,
            toolchain_identifier = "local",
            host_system_name = "local",
            target_system_name = "local",
            target_cpu = "k8",
            target_libc = "unknown",
            compiler = "clang",
            abi_version = "unknown",
            abi_libc_version = "unknown",
            tool_paths = tool_paths, # NEW
        )
    

    חשוב לוודא שנתיבי הנסיעה /usr/bin/clang ו-/usr/bin/ld הם הנתיבים הנכונים במערכת שלך.

  8. הרץ שוב את ה-build. Bazel מציג את השגיאה הבאה:

     ..../BUILD:3:1: undeclared inclusion(s) in rule '//main:hello-world':
     this rule is missing dependency declarations for the following files included by 'main/hello-world.cc':
     '/usr/include/c++/9/ctime'
     '/usr/include/x86_64-linux-gnu/c++/9/bits/c++config.h'
     '/usr/include/x86_64-linux-gnu/c++/9/bits/os_defines.h'
     ....
    

    Bazel צריכה לדעת היכן לחפש כותרות נכללות. יש כמה דרכים לפתור את הבעיה, למשל שימוש במאפיין includes של cc_binary, אבל כאן פותרים את הבעיה ברמת ה-keychain עם cxx_builtin_include_directories הפרמטר cc_common.create_cc_toolchain_config_info. יש לשים לב שאם אתה משתמש בגרסה אחרת של clang, נתיב ההכללה יהיה שונה. ייתכן שהנתיבים האלו יהיו שונים, בהתאם להתפלגות.

    יש לשנות את ערך ההחזרה ב-toolchain/cc_toolchain_config.bzl כך שייראה כך:

     return cc_common.create_cc_toolchain_config_info(
          ctx = ctx,
          cxx_builtin_include_directories = [ # NEW
            "/usr/lib/llvm-9/lib/clang/9.0.1/include",
            "/usr/include",
          ],
          toolchain_identifier = "local",
          host_system_name = "local",
          target_system_name = "local",
          target_cpu = "k8",
          target_libc = "unknown",
          compiler = "clang",
          abi_version = "unknown",
          abi_libc_version = "unknown",
          tool_paths = tool_paths,
     )
    
  9. מריצים שוב את פקודת ה-build. תוצג לכם שגיאה כמו:

    /usr/bin/ld: bazel-out/k8-fastbuild/bin/main/_objs/hello-world/hello-world.o: in function `print_localtime()':
    hello-world.cc:(.text+0x68): undefined reference to `std::cout'
    

    הסיבה לכך היא שבקישור חסר הספרייה C++ הרגילה, והוא אינו יכול למצוא את הסמלים שלו. אפשר לפתור את הבעיה בדרכים רבות, למשל באמצעות המאפיין linkopts של cc_binary. כדי לפתור את הבעיה, חשוב לוודא שכל יעד שמשתמש ב-chachain לא חייב לציין את הסימון הזה.

    מעתיקים את הקוד הבא אל cc_toolchain_config.bzl:

      # NEW
      load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
      # NEW
      load(
          "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
          "feature",
          "flag_group",
          "flag_set",
          "tool_path",
      )
    
      all_link_actions = [ # NEW
          ACTION_NAMES.cpp_link_executable,
          ACTION_NAMES.cpp_link_dynamic_library,
          ACTION_NAMES.cpp_link_nodeps_dynamic_library,
      ]
    
      def _impl(ctx):
          tool_paths = [
              tool_path(
                  name = "gcc",
                  path = "/usr/bin/clang",
              ),
              tool_path(
                  name = "ld",
                  path = "/usr/bin/ld",
              ),
              tool_path(
                  name = "ar",
                  path = "/bin/false",
              ),
              tool_path(
                  name = "cpp",
                  path = "/bin/false",
              ),
              tool_path(
                  name = "gcov",
                  path = "/bin/false",
              ),
              tool_path(
                  name = "nm",
                  path = "/bin/false",
              ),
              tool_path(
                  name = "objdump",
                  path = "/bin/false",
              ),
              tool_path(
                  name = "strip",
                  path = "/bin/false",
              ),
          ]
    
          features = [ # NEW
              feature(
                  name = "default_linker_flags",
                  enabled = True,
                  flag_sets = [
                      flag_set(
                          actions = all_link_actions,
                          flag_groups = ([
                              flag_group(
                                  flags = [
                                      "-lstdc++",
                                  ],
                              ),
                          ]),
                      ),
                  ],
              ),
          ]
    
          return cc_common.create_cc_toolchain_config_info(
              ctx = ctx,
              features = features, # NEW
              cxx_builtin_include_directories = [
                  "/usr/lib/llvm-9/lib/clang/9.0.1/include",
                  "/usr/include",
              ],
              toolchain_identifier = "local",
              host_system_name = "local",
              target_system_name = "local",
              target_cpu = "k8",
              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],
      )
    
  10. אם מפעילים את bazel build --config=clang_config //main:hello-world, הוא אמור להגיע לבנייה סופית.

בדיקת העבודות שלך

בסרטון הדרכה זה למדתם כיצד להגדיר כלי עבודה בסיסיים מסוג ++C, אך כלי כלים חזקים יותר מדוגמה פשוטה זו.

המסקנות העיקריות הן: - עליך לציין בשורת הפקודה --crosstool_top סימון שאמור להפנות לcc_toolchain_suite - ניתן ליצור קיצור דרך לתצורה מסוימת שימוש בקובץ .bazelrc - cc_toolchain_Suite עשוי לכלול cc_toolchains מעבדים ומהדרים שונים. אפשר להשתמש בסימונים של שורת פקודה כמו --cpu כדי להבדיל בין התכונות. - עליכם ליידע את מחזיק הכלים כדי לדעת היכן נמצאים הכלים. במדריך הזה יש גרסה פשוטה, שבה אפשר לגשת לכלים מהמערכת. אם חשוב לך יותר גישה עצמאית, אפשר לקרוא כאן על סביבות עבודה. ייתכן שהכלים שלך יגיעו מסביבת עבודה אחרת, ויהיה עליך להפוך את הקבצים לזמינים ב-cc_toolchain עם יעדי יעד במאפיינים, כגון compiler_files. יש לשנות גם את ה-tool_paths. - ניתן ליצור תכונות כדי להתאים אישית את הסימונים שיש להעביר לפעולות שונות, בין אם מדובר בקישור או בכל סוג אחר של פעולה.

קריאה נוספת

לקבלת פרטים נוספים, ראה הגדרת כלי C++