C++기반 프로젝트를 개발할 때 오픈소스 라이브러리를 사용하기 위해서는 다양한 방법이 있다. 원하는 패키지의 소스 코드를 다운로드하여 직접 빌드 하는 방법 혹은 apt 같은 패키지 매니저를 통해 설치하는 방법 등 표준화된 방법은 없지만 python의 pip, golang의 mod, node등 다른 언어로 개발할 때 사용되는 종속성 설치 도구와 비교하면 다소 불편한 것은 사실이다.

이런 불편함을 해소하기 위해 Microsoft에서 오픈소스 프로젝트로 멀티 플랫폼에서 사용하능한 C++ 패키지 매니저를 제공하고 있다. 본 글에서는 vcpkg를 설치 및 사용하는 방법에 대해서 다룰것이다.

설치

vcpkg repository에서 제공하는 스크립트를 통해 설치한다. 윈도우의 경우 쉘 스크립트가 아닌 bootstrap-vcpkg.bat 파일을 이용한다.

$ git clone https://github.com/microsoft/vcpkg
$ ./vcpkg/bootstrap-vcpkg.sh

스크립트가 정상적으로 실행되었다면 디렉터리 안에 vcpkg 바이너리가 생긴 것을 확인할 수 있다.

아래 도움말 명령어를 입력하여 정상적으로 실행이 되는지 확인한다.

$ ./vcpkg --version
vcpkg package management program version 2023-08-09-9990a4c9026811a312cb2af78bf77f3d9d288416

See LICENSE.txt for license information.

생성된 vcpkg 바이너리를 PATH에 등록해서 사용해도 되지만, 보통 프로젝트에서 vcpkg를 연동할 때는 클론받은 vcpkg 전체 디렉터리를 명시하는 경우가 많기 때문에 반드시 PATH에 등록하여 사용할 필요는 없다.

사용법

help 명령어를 치면 아래와 같이 기본적인 사용법을 알려준다.

Commands:
  vcpkg search [pat]              Search for packages available to be built.
  vcpkg install <pkg>...          Install a package.
  vcpkg remove <pkg>...           Uninstall a package.
  vcpkg update                    List packages that can be updated.
  vcpkg remove --outdated         Uninstall all out-of-date packages.
  vcpkg upgrade                   Rebuild all outdated packages.
  vcpkg hash <file> [alg]         Hash a file by specific algorithm, default SHA512.
  vcpkg help topics               Display the list of help topics.
  vcpkg help <topic>              Display help for a specific topic.
  vcpkg list                      List installed packages.

  vcpkg integrate install         Make installed packages available user-wide.
  vcpkg integrate remove          Remove user-wide integration.
  vcpkg integrate bash            Enable bash tab-completion.
  vcpkg integrate zsh             Enable zsh tab-completion.
  vcpkg integrate x-fish          Enable fish tab-completion.

  vcpkg export <pkg>... [opt]...  Exports a package.
  vcpkg edit <pkg>                Open a port for editing (use the environment variable 'EDITOR' to
                                  set an editor program, defaults to 'code').
  vcpkg create <pkg> <url> [archivename]
                                  Create a new port.
  vcpkg x-init-registry <path>    Initializes a registry in the directory <path>.
  vcpkg format-manifest --all     Formats all vcpkg.json files. Run this before committing to vcpkg.
  vcpkg owns <pat>                Search for files in installed packages.
  vcpkg depend-info <pkg>...      Display a list of dependencies for ports.
  vcpkg env                       Creates a clean shell environment for development or compiling.
  vcpkg version                   Display version information.
  vcpkg contact                   Display contact information to send feedback.

  @response_file                  Specify a response file to provide additional parameters.

For more help (including examples) see the accompanying README.md and docs folder.

예시로, 대표적인 c++ 로깅 라이브러리인 spdlog를 설치하고 싶다면 아래처럼 입력한다.

$ ./vcpkg install spdlog

명령어를 입력하면 결과 로그에서도 볼 수 있듯이 spdlog를 설치하는데 필요한 종속성을 모두 설치한다. vcpkg가 종속성을 처리하는 방법에 대해서는 나중에 다룰 것이다.

로그를 자세히 살펴보면 하나의 라이브러리를 설치하는데 보통 아래의 3단계를 거친다.

  1. 소스 코드 다운로드
  2. 컴파일
  3. 라이브러리 인스톨

1번 단계의 소스 코드 다운로드는 vcpkg 디렉터리의 downloads 디렉터리에 저장되고, 2번 단계의 컴파일 결과는 packages 디렉터리, 마지막으로 3번 단계의 결과는 installed 디렉터리에 저장된다.

단순히 prebuilt binary를 받는게 아닌 각 환경에 맞게 소스 코드를 다운로드 받고 컴파일 & 설치하는 과정이기 때문에 Boost등 제법 큰 규모의 라이브러리를 설치할 때 꽤 시간이 소요될 수 있다.

다양하게 빌드하기 (feat. triplet)

설치가 완료된 폴더인 installed 디렉터리를 확인해보자. 각 환경마다 다르지만, x86_64 아키텍처의 리눅스 환경이라면 x64-linux 디렉터리 내부에 라이브러리가 설치된 것을 확인할 수 있다.

여기서 vcpkg에서는 triplet 라는 용어를 사용한다. CPU 아키텍처, OS, 컴파일러, 런타임 등을 명시하기 위한 방법으로 멀티 플랫폼, 크로스 컴파일 등 단일 라이브러리에 대한 여러 타겟 세트를 위해 사용된다.

예를들어 openssl은 정적 라이브러리를 사용하고 zlib는 공유 라이브러리를 사용한다고 하면 각 triplet은 x64-linux, x64-linux-dynamic을 사용한다.

위에서 설치한 spdlog를 예시로 들면, 정적 라이브러리가 아닌 공유 라이브러리를 사용하고자 할 때 설치할 라이브러리 뒤에 triplet을 명시하는 방법으로 사용한다.

$ ./vcpkg install spdlog:x64-linux-dynamic

또는 아래처럼 --triplet옵션을 사용하는 방법도 있다.

$ ./vcpkg install spdlog --triplet=x64-linux-dynamic

일반적으로 사용하는 triplet 목록을 보려면 ./vcpkg help triplet으로 확인할 수 있다.

사용가능한 triplet은 각 호스트 PC 환경마다 다르다. 예를들어, Linux환경에서 arm64 크로스 컴파일러를 이용하여 arm64-linux 을 사용하는 것은 가능하지만 윈도우 타겟의 x64-windows triplet은 사용할 수 없다.


프로젝트에 적용하기

설치된 라이브러리들을 프로젝트에서 사용하려면 어떻게 해야할까? installed 디렉터리에 설치된 라이브러리를 하나하나 링크하는 방법? 각 라이브러리의 경로를 module path에 추가하는 방법? 모두 가능 하지만 이런 방법을 쓰는것은 vcpkg를 사용하는 의미가 없다.

클래식 모드 (Classic mode)

vcpkg에서는 프로젝트에서 라이브러리를 인식할수 있도록 cmake파일을 제공하고 있다. cmake configuration 단계에서 아래의 옵션을 추가하자.

-DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake

해당 옵션을 통해 cmake toolchain을 구성하고 find_package()를 통해 별도의 경로지정없이 라이브러리를 사용할 수 있게된다.

만약 find_package()로 라이브러리 탐색에 실패한 경우 라이브러리 타겟 이름이 올바르게 설정되지 않았을 수도 있으니 타겟 이름이 라이브러리에서 설정된 이름과 일치하는지 확인한다.

추가로 라이브러리 설치 후에 나오는 로그를 자세히 살펴보면 아래처럼 usage가 명시되는 경우도 있으니 이를 참고한다.

find_package(spdlog CONFIG REQUIRED)
target_link_libraries(main PRIVATE spdlog::spdlog)

# Or use the header-only version
find_package(spdlog CONFIG REQUIRED)
target_link_libraries(main PRIVATE spdlog::spdlog_header_only)

매니페스트 모드 (Manifest mode)

클래식 모드는 프로젝트에서 필요한 라이브러리를 일일이 수동으로 설치해야한다는 단점이 있다. 다행히도 vcpkg에서는 프로젝트 종속성 및 빌드 구성이 가능한 매니페스트 모드를 지원한다.

사용 방법은 프로젝트 최상단 디렉터리에 vcpkg.json파일을 생성하고 종속성을 명시하면 된다.

{
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
  "name": "libfoo",
  "version": "0.0.1",
  "dependencies": [
    "zlib",
    "fmt"
  ]
}

예시로 libfoo라는 프로젝트에서 zlib, fmt 라이브러리를 사용한다고 하면 위와 같이 작성하면 된다.

클래식 모드와 마찬가지로 CMAKE_TOOLCHAIN_FILE옵션을 지정해주면 매니페스트 파일에 명시된대로 각 라이브러리 빌드 과정 및 설치를 수행한다.

참고로 위의 예제에서 $schema 필드는 json 스키마를 명시한다. 매니페스트 모드에서 지원하는 각 필드의 autocomplete 및 유효성 검사를 위해 사용한다.

버전 지정하기

라이브러리를 사용하다보면 특정 이유로 버전을 지정해야 할 때가 종종 있다. 매니페스트 모드를 사용하면 특정 버전도 지정할 수 있다.

{
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
  "name": "libfoo",
  "version": "1.0.0",
  "dependencies": [
    "zlib",
    "fmt"
  ],
  "builtin-baseline":"3426db05b996481ca31e95fff3734cf23e0f51bc",
  "overrides": [
    { "name": "fmt", "version": "6.0.0" }
  ]
}

위처럼 fmt 라이브러리는 overrides 필드를 사용하여 재정의 되었으므로, 최신버전이 아닌 6.0.0버전을 사용하게 된다.

또한, 버전 지정 기능을 사용하려면 builtin-baseline 필드를 활성해 주어야 사용할 수 있는데 이 필드는 vcpkg 레지스트리의 기준선을 의미하며 일반적으로 커밋 해쉬값(SHA1)을 사용한다.

이외에 더 자세한 설명은 해당 문서를 참고한다.

마무리

이번 글에서는 vcpkg를 사용하는 기초적인 방법에 대해서 알아보았다. 다음 포스팅에서는 나만의 vcpkg 레지스트리를 만드는 방법에 대해서 알아볼 것이다.