Bazel 빌드 시스템 반나절 사용기

cpp

(Deneb) #1

코드를 어느정도 작성하다보면, 의존 헤더와 라이브러리가 프로젝트에 헝클어져 있는 것을 종종 볼 수 있습니다. 이 문제를 해결하는 많은 프로그램이 존재하는데요, 보통 언어와 OS에 따라 많이 달라지긴 합니다.

가령 Node의 경우, npm이라는 패키지 매니저를 많이 쓰고 파이썬의 경우 pip이 있죠.

C++의 경우 비교적 오래된 언어고 많은 작업을 프로그래머에 맡기는 성격을 가지고 있다보니, 역사적으로 많은 빌드 시스템과 패키지 매니저가 공존했습니다.

make 부터 autoconf/automake, CMake, SCons 등의 빌드 시스템과 vcpkg, biicode 등의 패키지 매니저가 있어 같이 쓰기도 하는데요, 오늘은 구글에서 개발한 Bazel이란 녀석을 사용기와 몇 가지 개선해야 할 점 등을 한 번 써보고자 합니다.

https://www.bazel.build/


Bazel은 C++ 프로젝트에도 사용할 수 있지만 태생이 다양한 언어 지원이다보니, 자바, Objective-C, 파이썬, 쉘 스크립팅 등도 지원하고 한 프로젝트 안에 여러 언어를 지원할 수도 있습니다.

제가 처음에 가장 매력있어 보였던 것이 간결한 문법과 디렉토리 구조인데요, Bazel은 크게 두 종류의 파일이 프로젝트 안에서 대부분의 역할을 합니다. WORKSPACEBUILD.

WORKSPACE는 프로젝트마다 하나만 있어야합니다. BUILD는 한 프로젝트에 한 개 이상 (보통 여러 개) 쓰고 타깃을 지정할 때 씁니다.

이렇게 파일명부터 어디가 프로젝트고 어디가 타깃인지 구분하기 쉽습니다. CMakeLists.txt와는 다르죠?

하지만 WORKSPACE에 쓸 수 있는 기능과 BUILD에 쓸 수 있는 기능이 다릅니다? Bazel에선 rule 이라 부릅니다. 함수라고 생각하셔도 무방할 듯…

C/C++를 예로 들면 cc_binary, cc_library, cc_import, cc_test 등의 rule이 있습니다. 각 rule마다 쓸 수 있는 인수가 다르고 공통인 것도 있습니다. 자세한 건 공식 문서 참고…

약간 다른 WORKSPACE와 BUILD지만 Starlark 언어라는 DSL을 쓴다는 건 같습니다. 파이썬과 문법이 비슷하죠. 일례로 glob(["src/*.cc"]) + ["a.h"]) 따위로 쓸 수 있습니다.

$ bazel build 타깃명 으로 빌드를 하면 WORKSPACE가 있는 폴더에 자동으로 여러 출력 폴더와 바이너리 파일을 생성해 줍니다.


긴 말 필요없고 예시를 보여달라…면 보여드려야죠. 쉽습니다.

팩토리얼 라이브러리를 테스트와 같이 구현한 간단한 C++ 프로젝트입니다.

// include/factorial.h
#pragma once

unsigned int factorial(unsigned int n);
# BUILD
cc_library(name = "factorial", srcs = glob(["src/*.cc"]), includes=["include"], visibility=["//visibility:public"])
// src/factorial.cc
#include "factorial.h"

unsigned int factorial(unsigned int n) {
  return n ? n * factorial(n - 1) : 1;
}
# test/BUILD
cc_test(name = "factorialtest", deps = [ "//:factorial", "@gtest//:gtest", "@gtest//:gtest_main" ], srcs = ["factorial.cc"])
// test/factorial.cc
#include <gtest/gtest.h>
#include "factorial.h"
TEST(FactorialTest, CheckSanity) {
  EXPECT_EQ(factorial(0), 1);
  EXPECT_EQ(factorial(1), 1);
  EXPECT_EQ(factorial(2), 2);
  EXPECT_EQ(factorial(5), 120);
}
# WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(name = "gtest", sha256 = "927827c183d01734cc5cfef85e0ff3f5a92ffe6188e0d18e909c5efebf28a0c7", strip_prefix = "googletest-release-1.8.1", url = "https://github.com/google/googletest/archive/release-1.8.1.zip")


단점은… 일단 CMake보단 사용자 수가 적고 아직 beta 딱지도 못 뗀 신생 프로그램이다보니 이리저리 골치아픈 게 있네요. 예를들어, BUILD 파일을 제공하지 않는 외부 라이브러리는 직접 작성해줘야 한다는 치명적인… 단점이 있습니다. GTest는 역시 구글이 만든 라이브러리라 그런지 Bazel 파일을 기본으로 제공해서 괜찮았지만요. 또한 빌드를 한 번 하면 해당 디렉토리의 핸들을 잡고 있기 때문에 shutdown을 해 줘야 폴더를 지울 수 있습니다.

그리고 지원하는 IDE가 매우 적습니다. 그나마 공식적으로 지원하는 IDE는 VS Code 뿐? 인데… 뭐 몇가지 불편함을 감수한다면 VS Code에서도 쓸만하더군요.

이런 거 보면 CMake가 “그나마” 편하긴 합니다… 대부분의 IDE에서 지원하고 윈도우에서도 vcpkg랑 연동이 되잖아요.

아무튼 부족한 글 읽어주셔서 감사합니다 :wink:


(9791344f4846a0c025e03a1ada59cabb) #2

Tensorflow 쪽에서 bazel을 많이 쓰더라구요

https://www.tensorflow.org/install/source

소스부터 빌드하려면 bazel로 해야합니다

https://www.tensorflow.org/lite/demo_android#build_tensorflow_lite_and_the_demo_app_from_source

Tensorflow를 안드로이드에서 쓰려면 Bazel로 빌드해야 가능합니다

윈도우에서는 빌드를 못한다는 :joy:

그외에도


여기 목록에 보시면 bazel 이용하는 프로젝트들이 많이 있습니다