building system/bazel

외부 의존성이 있는 bazel 기본 예제 구현하기

opencpp 2024. 1. 20. 17:16
반응형

개요

bazel 5.0 이후에 bzlMod라고 부르는 새로운 형태의 외부 의존성 설계 모델이 bazel에 도입되었고, bazel 설계팀에서 "bazel의 미래"라고 언급했습니다. 우리가 가야 할 방향은 최종적으로 bzlMod 기반으로 전체 빌드 시스템을 구축하는 것입니다.

우선 새로운 의존성 설계 모델을 곧바로 도입하기 전에, 이전 설계 모델 예제를 먼저 소개합니다. 본인이 판단하기에 작은 단위 빌드 시스템을 구축할 때 여전히 도움이 되고, 새로운 의존성 설계 모델은 기존 설계 모델을 변형함으로써 자연스럽게 bzlMod 도입을 유도하고 있습니다. 즉, 새로운 의존성 설계 모델을 이해하는데 도움이 됩니다. 또한 많은 기존 빌드 시스템이 이전 방식으로 운영되고 있습니다.

예제에 사용할 폴더 구조 이해하기

폴더 구조는 간단합니다.
project1 폴더는 자신만의 빌드 시스템을 사용하고 있습니다. 하지만 어느날, 문득 project2 폴더의 빌드 시스템을 참조할 필요가 생기게 됩니다.

예를 들어 SDK 폴더에 third party 라이브리러리를 미리 사용하기 좋도록 빌드 환경을 구축한 후, 실제 라이브러리를 다른 팀이 사용하도록 설계할 수 있습니다. SDK 폴더를 인터넷 망으로 공유하고 곧바로 다른 팀에서 인터넷 망을 외부 의존성으로 설정할 수 있습니다. 이 부분에 다음 게시글에서 언급해보죠.

├─project1
│  │  BUILD.bazel
│  │  WORKSPACE.bazel
│  │
│  └─src
│          main.cc
│
└─project2
    │  BUILD.bazel
    │  WORKSPACE.bazel
    │
    └─src
        │  lib.cc
        │
        └─include
                lib.h

의존하게 될 project2 코딩 구현하기

실제적으로 의존하는 대상은 가시성 부분 설정해주고, 나머지는 기본 빌드 시스템과 차이가 없습니다. 주석 정보 추가는 정도 외에 딱히, 추가할 작업이 없습니다.

#project2/ WORKSPACE.bazel

src/ lib.cc 코딩은 단순합니다. 그냥 자신의 폴더 위치를 반환하는 call 함수를 정의할 뿐이죠.

// project2/src/lib.cc
const char* call() { return "project2/src";}

src/include/lib.h는 lib.cc를 사용하려는 외부 사용자이 사용할 수 있도록 선언만을 단순히 정의합니다.

//project2/src/include/lib.h
const char* call();

project2 빌드 시스템 구현하기

빌드 시스템을 구현할 맨 상위 폴더, 즉 project2에 WORKSPACE.bazel를 생성합니다. WORKSPACE.bazel 파일의 존재 여부를 통해, 빌드 시스템의 최상위 폴더를 확인하는 용도로 사용됩니다. project2는 다른 빌드 시스템 경로는 의존하지 않음으로, 추가로 작업이 없습니다. 그냥 존재만하고, 비어두면 됩니다.

BUILD.bazel 파일은 빌드 target을 rule 문법을 통해 정의합니다. 여기에서 적용할 rule은 C++/C에 맞게 정의한 cc_* rule를 사용합니다. 필요하면 rule를 bzl 파일로 정의할 수 있습니다. rule의 이름은 프로그래머에게는 function의 이름과 유사합니다.

cc_library rule를 통해 lib 라이브러리 타겟을 정의합니다. srcs에는 컴파일에 참여할 파일 이름 목록을, includes에는 include directory에 참여할 폴더 목록을 정의합니다. 이렇게 정의한 include directory 목록은 의존성으로 사용된 프로젝트의 include directory 목록으로 전파됩니다. 마지막으로 'visibility' 인자에 외부에서 사용할 수 있도록 가시성으로 public으로 설정합니다.

가시성 설정 구문을 보면, 전체 가시성 정보를 tree 형태로 관리하고 있음을 인지할 수 있습니다.
srcs에 추가된 파일 목록은 lib 폴더에 의존성이 걸리게 됩니다. 즉 srcs의 파일 목록이 변경되면, lib target이 다시 빌드됩니다.

cc_library(
    name = "lib",
    srcs = ["src/lib.cc"],
    includes = ["src/include"],
    visibility = ["//visibility:public"]
)

project2 전체 빌드 시스쳄이 완성되었습니다. 이제 컴파일하고 실행하면 됩니다. project2 폴더에서 cmd로 다음과 같음 명령어를 입력합니다.

bazelisk build //...

위 명령어는 bazelisk를 통해 build하는데, 맨 상위 경로(//)에 포함된 모든 target(...)를 대상한다는 의미합니다.

// 본인 결과 화면
D:\test\bazel\project2>bazelisk build //...
INFO: Analyzed target //:lib (66 packages loaded, 298 targets configured).
INFO: Found 1 target...
Target //:lib up-to-date:
  bazel-bin/lib.lib
INFO: Elapsed time: 1.632s, Critical Path: 0.63s
INFO: 4 processes: 2 internal, 2 local.
INFO: Build completed successfully, 4 total actions

D:\test\bazel\project2>

project1 코딩 구현하기

project1/src/main.cc 코딩은 단순합니다. 밑도 끝도 없이 lib.h를 include하고 call() 함수를 밑도 끝도 없이 사용합니다. 결국 lib.h 파일을 찾도록 해주고, call 함수를 호출할 수 있도록 라이브러리를 link해주는 것이 빌드 시스템이 해야 할 일입니다. 아직은 쓸모없는 코딩, 생명력이 없는 코딩, 죽은 코딩과 같죠!!

//project1/src/main.cc
#include <lib.h>
#include <iostream>

int main(){
    std::cout<< call();
    return 0;
}

project 1 빌드 시스템 구축하기

역시 맨 상위 project1 폴더에 WORKSPACE.bazel를 생성합니다. 따라서 project1 하부 폴더를 모두 빌드 시스템이 관리합니다. 빌드시스템의 맨 상위 폴더를 표현하는 역할 외에 외부의 다른 빌드 시스템을 참고해야 합니다. 따라서 이번에는 관련 rule를 적용해야 합니다.

이번에 적용할 rule는 *_repository rule 중에 local_repository rule입니다. name 항목으로 외부 repository 이름을 저정합니다. 외부 repository 이름은 @<repository 이름>//으로 참고할 수 있습니다. path 항목으로 옆에 친구 project2 폴더를 지정합니다. 따라서 ..로 위로 올라가서 project2를 참고하면 됩니다.
project1 폴더의 하부 폴더에서 @project2를 통해서 path로 지정한 경로로 접근하게 됩니다.

#projec1/ WORKSPACE.bazel
local_repository(
    name = "project2",
    path = "../project2"
)

마지막 project1/BUILD.bazel 파일을 통해 project1/src/main.cc에 생명력을 불어 넣어 주어야 합니다.
이번에는 cc_binary rule를 사용해 실행 파일을 만들고 있습니다. main target를 생성하고, srcs로 컴파일에 참여할 파일 목록을 지정합니다. deps 항목에서 외부 의존성 구문이 등장합니다.

@project2 구문에서 @project2는 맨 상위 폴더 /project1/WORKSPACE.bazel에 정의한 외부 의존성 target 이름을 추적합니다.
//과 : 사이에서는 얻어낸 폴더에서 물리적인 하부 폴더를 지정합니다. 예를 들어 @project2//src/include 이런 식으로. 찾는 폴더에는 최종적으로 BUILD.bazel 파일이 있어야 합니다. :lib는 rule로 정의한 lib target 이름입니다.

cc_binary(
    name = "main",
    srcs =  ["src/main.cc"],
    deps = ["@project2//:lib"],
)

이젠 빌드할 시간입니다. 역시 project1 폴더에서 cmd 프로그램을 수행하고 다음 명령을 입력합니다. 자신의 빌드 시스템만 빌드하면, 자동으로 의존성 빌드 시스템에서 필요한 부분을 컴파일합니다.

bazelisk build //...

이젠 빌드한 결과를 실행해야 겠죠. 빌드 시스템의 맨 상위에 BUILD.bazel 파일이 있기 때문에, 곧바로 //:로 시작하고, BUILD.bazel에서 main target를 수행해야 합니다.

bazelisk run //:main

본인의 실행 결과는 아래와 같습니다. 컴파일 과정을 진행한 후, 마지막 "project2/src" 구문이 출력된 것을 확인할 수 있습니다.

D:\test\bazel\project1>bazelisk run //:main
INFO: Analyzed target //:main (67 packages loaded, 304 targets configured).
INFO: Found 1 target...
Target //:main up-to-date:
  bazel-bin/main.exe
INFO: Elapsed time: 1.047s, Critical Path: 0.01s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/main.exe
project2/src
D:\test\bazel\project1>

위 설명 과정에 사용한 파일

project.zip
1.5 kB

반응형