সরাসরি পর্বে যান
পর্ব ৫০ · ৫৮-এর মধ্যে · মডিউল ৭

Package management — vcpkg আর Conan দিয়ে বাইরের লাইব্রেরি আনা

Package Management in C++ — Dependencies with vcpkg and Conan

Taught in Bangla, this lesson shows how to bring external libraries into C++ projects: why ABI makes distribution hard, comparing FetchContent, CPM, vcpkg and Conan, vcpkg manifests, baselines, triplets and binary caching, Conan 2 profiles, generators and lockfiles, linking fmt, spdlog, GoogleTest and Boost in CMake, and licensing and supply-chain safety.

এই পর্বে যা শিখবেন

  • Header-only আর compiled লাইব্রেরি, static আর shared — আর ABI কেন C++-এ লাইব্রেরি বিতরণ কঠিন করে
  • Source কপি, apt/brew, FetchContent, vcpkg, Conan — কোন পথ কখন, একটি তুলনা-টেবিলে
  • vcpkg-এর manifest, baseline, version, triplet, binary cache আর overlay port
  • Conan 2-এর conanfile, profile, generator, lockfile আর remote
  • fmt, spdlog, nlohmann_json, GoogleTest আর Boost CMake-এ জোড়া; CI, লাইসেন্স, supply-chain নিরাপত্তা — আর AI-এর ভুল package-নাম ধরা

১ · “লাইব্রেরিটা শুধু নামিয়ে নিই” — C++-এ কেন এত সহজ নয়

Python শেখার সময় আপনি হয়তো pip install requests লিখেছেন, আর পরের লাইনেই import requests. Java-তে Maven-এর ফাইলে একটি লাইন যোগ করলেই লাইব্রেরি চলে আসে। JavaScript-এ npm, Rust-এ cargo. সব আধুনিক ভাষার একটি নিজস্ব package manager আছে, যা ভাষার সাথেই আসে।

C++ এভাবে বড় হয়নি — কোনো “অফিসিয়াল” লাইব্রেরি-বাজার তৈরি হয়নি। এর পেছনে একটি প্রযুক্তিগত কারণও আছে, আর সেটা বুঝলেই এই পর্বের বাকিটা সহজ।

লাইব্রেরি তিন রকম চেহারায় আসে

Header-only

শুধু .h/.hpp ফাইল, আলাদা compile নেই। header-only library আপনার কোডের সাথেই compile হয়, তাই ABI-র চিন্তা কম। যেমন nlohmann/json, Boost-এর বড় অংশ।

Static

আগে compile করা .a (Linux) বা .lib (Windows). Link-এর সময় কোড আপনার প্রোগ্রামে কপি হয়। একে বলে static library.

Shared

.so, .dylib বা .dll. প্রোগ্রাম চলার সময় আলাদা ফাইল থেকে লোড হয়; কয়েকটি প্রোগ্রাম একটি কপি ভাগ করে। একে বলে shared library.

শেষ দুই ধরনে লাইব্রেরি আসে machine code হিসেবে। আর machine code-এর সাথে আপনার কোড জোড়া লাগবে কি না, তা ঠিক করে একটি অদৃশ্য চুক্তি — ABI (Application Binary Interface). পর্ব ০৩-এ দেখেছেন, C++ compiler প্রতিটি ফাংশনের নাম বদলে একটি লম্বা চিহ্ন বানায় — name mangling. GCC আর MSVC এই কাজ ভিন্ন নিয়মে করে। শুধু নাম নয়, ABI-তে আরও অনেক কিছু থাকে:

যা মিলতে হবেউদাহরণনা মিললে
Compiler পরিবারGCC/Clang বনাম MSVCName mangling আলাদা — undefined reference
Standard librarylibstdc++, libc++, MSVC STLstd::string-এর ভেতরের গঠন আলাদা — link error বা crash
Build typeDebug বনাম Release (বিশেষত MSVC-তে)Debug-এ container-এর আকার আলাদা, runtime আলাদা
Runtime libraryMSVC-র /MD বনাম /MTদুই কপি C runtime, একটির new অন্যটির delete — crash
প্ল্যাটফর্ম ও আর্কিটেকচারx64 Linux, x64 Windows, ARM64 Macটাইপের আকারই আলাদা হতে পারে
কিছু flag আর macro_GLIBCXX_USE_CXX11_ABI, -fno-exceptionsএকই নামের টাইপ, ভিন্ন গঠন

ঢাকার একটি গার্মেন্টস কারখানা চীন থেকে বোতাম আনে। শুধু “বোতাম” বললে চলে না — মাপ, ছিদ্রের সংখ্যা, রং, কাপড়ের ধরন সব মিলতে হয়, নইলে সেলাই মেশিনে বসবে না। Python-এর লাইব্রেরি যেন সুতা — যেকোনো মেশিনে চলে। C++-এর compiled লাইব্রেরি যেন বোতাম — আপনার মেশিনের মাপে বানানো না হলে লাগবে না। Package manager হলো সেই সাপ্লায়ার, যে আপনার মাপ জেনে ঠিক বোতামটি পাঠায়, দরকার হলে আপনার কারখানাতেই বানিয়ে দেয়।

নিজের মেশিনের এই “মাপ” একটি ছোট প্রোগ্রাম দিয়েই দেখা যায়। নিচের কোড compiler, standard library আর build ধরন নিজেই জানিয়ে দেয় — ঠিক যে তথ্যগুলো একটি package manager লাইব্রেরি build করার আগে মিলিয়ে নেয়:

#include <print>
#include <version>
int main() {
#if defined(__clang__)
    std::println("compiler        : Clang");
#elif defined(__GNUC__)
    std::println("compiler        : GCC");
#endif
#if defined(__GLIBCXX__)
    std::println("standard library: libstdc++ (new string ABI = {})", _GLIBCXX_USE_CXX11_ABI);
#elif defined(_LIBCPP_VERSION)
    std::println("standard library: libc++");
#endif
#ifdef NDEBUG
    std::println("build style     : Release (NDEBUG defined)");
#else
    std::println("build style     : Debug (NDEBUG not defined)");
#endif
    std::println("sizeof(long)    : {} bytes", sizeof(long));
}

পাতার রানার Linux-এ GCC চালায়, তাই দেখাবে GCC, libstdc++, Debug আর sizeof(long) = 8. একই কোড Windows-এ MSVC দিয়ে চালালে প্রথম দুই লাইন বাদ পড়বে (ওই macro-গুলো MSVC-তে নেই), আর sizeof(long) হবে 4 — কারণ 64-bit Windows LLP64 নিয়ম মানে (long 32-bit), আর 64-bit Linux মানে LP64 (long 64-bit). অর্থাৎ যে লাইব্রেরির public ফাংশন long নেয়, তার binary দুই প্ল্যাটফর্মে আলাদা হতেই হবে। new string ABI = 1 অংশটি একটি পুরোনো যুদ্ধের চিহ্ন — ভাবনার প্রশ্ন ০৩-এ এর গল্প।

এখন একটি বাস্তব দুর্ঘটনা ধাপে ধাপে দেখুন। রাফি একটি দোকানের হিসাবের প্রোগ্রাম লিখছেন, আর এক সহকর্মী তাঁকে ইমেইলে একটি আগে-build-করা fmt লাইব্রেরি পাঠিয়েছেন:

  1. পাওয়া লাইব্রেরিfmt.libMSVC, Release, /MD
  2. MinGW g++undefined referenceভিন্ন mangling
  3. MSVC, DebugLNK2038Debug বনাম Release
  4. Linux-এর গল্প__cxx11পুরোনো string ABI
  5. সমাধানtriplet / profileআপনার মাপে build
সহকর্মীর ইমেইলশুধু ফাইল, কোনো তথ্য নেই
fmt/
├── include/fmt/format.h
└── lib/fmt.lib      # কোন compiler? কোন build type? লেখা নেই
# আসলে: Visual Studio 2022, Release, /MD দিয়ে বানানো
$ g++ main.cpp -Ifmt/include fmt/lib/fmt.libMinGW (GCC)
undefined reference to `fmt::v10::vformat(fmt::v10::basic_string_view<char>, ...)'
# g++ খুঁজছে _ZN3fmt3v107vformat... নামের চিহ্ন
# fmt.lib-এ আছে ?vformat@v10@fmt@@... (MSVC-র mangling)
Visual Studio, Debug buildMSVC
error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL':
  value '0' doesn't match value '2' in main.obj
error LNK2038: mismatch detected for 'RuntimeLibrary':
  value 'MD_DynamicRelease' doesn't match value 'MDd_DynamicDebug'
Linux: পুরোনো সার্ভারে বানানো libreport.soGCC
undefined reference to `report::save(std::__cxx11::basic_string<char> const&)'
# আপনার কোড নতুন ABI-র std::string চায় (__cxx11)
# লাইব্রেরিটি _GLIBCXX_USE_CXX11_ABI=0 দিয়ে বানানো
Package manager-এর পথএকই toolchain, দুই build type
vcpkg : triplet  x64-windows  → Debug আর Release দুই কপিই বানায়
Conan : profile  compiler=msvc, build_type=Debug
# লাইব্রেরি আপনার compiler, runtime আর build type দিয়েই তৈরি
# ফলে link-এ কোনো অমিল নেই
  1. শুধু ফাইল এসেছে, পরিচয় আসেনি। একটি .lib ফাইল দেখে বোঝার সহজ উপায় নেই এটি কোন compiler, কোন runtime, কোন build type দিয়ে বানানো। অথচ এই তিনটিই ABI-র অংশ। “লাইব্রেরি পাঠিয়ে দাও” পদ্ধতির মূল দুর্বলতা এখানেই।
  2. ভিন্ন compiler. রাফি MinGW (Windows-এ GCC) দিয়ে build করলেন। GCC আর MSVC একই ফাংশনের জন্য সম্পূর্ণ ভিন্ন চিহ্ন বানায়, তাই linker ফাংশনটি খুঁজেই পায় না। Error দেখে মনে হয় “লাইব্রেরিতে ফাংশন নেই”, অথচ আছে — শুধু অন্য ভাষায় লেখা নাম।
  3. একই compiler, ভিন্ন build type. এবার Visual Studio, কিন্তু Debug. MSVC-র Debug build-এ STL container-এর ভেতরে বাড়তি যাচাইয়ের তথ্য থাকে (_ITERATOR_DEBUG_LEVEL), ফলে std::vector-এর আকারই আলাদা; runtime library-ও আলাদা। ভাগ্য ভালো, MSVC এটা link-এর সময় ধরে ফেলে। সব প্ল্যাটফর্ম এত দয়ালু নয়।
  4. একই compiler, ভিন্ন standard library ABI. Linux-এ GCC ৫ (২০১৫) থেকে std::string-এর নতুন গঠন এসেছে, নাম std::__cxx11::basic_string. পুরোনো ধাঁচে বানানো লাইব্রেরির সাথে নতুন কোড জোড়া লাগে না। সবচেয়ে বিপজ্জনক ক্ষেত্রে link হয়েও যায়, আর ভুল গঠনের ডেটা পড়ে প্রোগ্রাম চলার সময় crash করে।
  5. সমাধান: লাইব্রেরি আপনার মাপে বানানো। vcpkg-এর triplet আর Conan-এর profile ঠিক এই তথ্যগুলো (compiler, আর্কিটেকচার, runtime, build type) লিখে রাখে, আর লাইব্রেরিকে সেই অনুযায়ী build করে বা মিলে যাওয়া আগে-বানানো কপি আনে। নিয়মটা মনে রাখুন: একটি প্রোগ্রামের সব অংশ একই toolchain আর মিলে যাওয়া সেটিং দিয়ে তৈরি হওয়া চাই।
C জানলে

C-তে static আর shared লাইব্রেরি, -I, -L, -l — সব একই। পার্থক্য হলো C-র ABI অনেক সরল আর স্থির: name mangling নেই, template নেই, std::string-এর মতো জটিল টাইপ নেই। এজন্য একটি প্ল্যাটফর্মে GCC-তে বানানো C লাইব্রেরি প্রায়ই Clang দিয়েও চলে, আর অনেক C++ লাইব্রেরি বাইরের জগতের জন্য extern "C" ইন্টারফেস দেয়। হাতে লাইব্রেরি বানানো আর link করা ধাপে ধাপে দেখতে C কোর্সের মডুলার প্রোগ্রাম — header, Makefile, library পর্বটি পড়ুন।

২ · পুরোনো পথগুলো — source কপি, apt/brew আর “dependency hell”

Package manager আসার আগে C++ দলগুলো তিনটি পথে বাইরের লাইব্রেরি আনত। তিনটিই আজও দেখা যায়, তাই এদের সুবিধা আর সীমা জানা দরকার।

পথ ১ — source কপি করে repository-তে রাখা

লাইব্রেরির পুরো source নিজের প্রজেক্টের third_party/ ফোল্ডারে কপি করে Git-এ রেখে দেওয়া — এর নাম vendoring. সুবিধা: ইন্টারনেট ছাড়াই build হয়। খরচ: নিরাপত্তা-ত্রুটির খবর নিজে রাখতে হয়, নতুন version হাতে কপি করতে হয়, আর কপি করা কোডে কেউ “ছোট একটা ঠিক” করে দিলে পরে আপডেট প্রায় অসম্ভব। ছোট header-only লাইব্রেরির জন্য এটা এখনও যুক্তিসংগত; বড় লাইব্রেরির জন্য নয়।

পথ ২ — অপারেটিং সিস্টেমের package manager

sudo apt install libfmt-dev libspdlog-dev   # Ubuntu / Debian
brew install fmt spdlog                       # macOS (Homebrew)

Linux-এর apt, dnf বা macOS-এর Homebrew-কে বলে system package manager. এরা লাইব্রেরি আপনার সিস্টেমের compiler দিয়ে আগেই build করে রাখে, তাই ABI মিলে যায়, আর find_package সহজেই খুঁজে পায়। সীমা তিনটি:

  • একটিই version: পুরো সিস্টেমে প্রতিটি লাইব্রেরির একটি কপি। দুটি প্রজেক্টের দুটি ভিন্ন version লাগলে বিপদ।
  • Version আপনার হাতে নয়: Ubuntu-র একটি পুরোনো রিলিজে fmt পুরোনো, Arch-এ নতুন। দলের দুজনের মেশিনে দুই version — “আমার মেশিনে তো চলে!”
  • Windows-এ নেই: Windows-এর কোনো সর্বজনীন system package manager C++ লাইব্রেরি এভাবে দেয় না।

পথ ৩ — হাতে build করে কোথাও বসানো

লাইব্রেরির source নামিয়ে নিজে CMake চালিয়ে build আর install করা, তারপর সেই ফোল্ডার CMAKE_PREFIX_PATH-এ দেওয়া। কাজ করে, কিন্তু প্রতিটি নতুন মেশিন আর version-এ একই কাজ আবার। আর লাইব্রেরির নিজেরও নির্ভরতা থাকে — spdlog নিজেই fmt-এর ওপর নির্ভর করতে পারে। এই transitive dependency-গুলো হাতে মেলাতে গিয়েই জন্ম নেয় বিখ্যাত dependency hell.

Dependency hell-এর সবচেয়ে পরিচিত রূপ হলো diamond dependency: আপনার প্রোগ্রাম দুটি লাইব্রেরি ব্যবহার করে, আর দুজনেই তৃতীয় একটি লাইব্রেরির ওপর নির্ভর করে — কিন্তু ভিন্ন version-এ। নিচে ধাপে ধাপে দেখুন:

shop_appআপনার প্রোগ্রাম
spdlog 1.13চায় fmt ≥ 10
old_report 2.1চায় ঠিক fmt 8.x
fmt 10.2নতুন
fmt 8.1পুরোনো
নির্ভরতার graphদেখতে নিরীহ
shop_app
├── spdlog      # logging
└── old_report  # পুরোনো রিপোর্ট লাইব্রেরি, অন্য দলের লেখা
# দুজনেরই ভেতরে fmt লাগে
spdlog-এর চাহিদাfmt ≥ 10
spdlog 1.13 ──► fmt 10.x
# spdlog-এর header-এ fmt-এর টাইপ সরাসরি ব্যবহৃত হয়
old_report-এর চাহিদাঠিক 8.x
old_report 2.1 ──► fmt 8.x
# fmt 9-এ কিছু পুরোনো API বদলেছে, old_report আর আপডেট হয়নি
সংঘাতএক প্রোগ্রামে এক fmt
fmt::v10::vformat  বনাম  fmt::v8::vformat
# দুটোই link করলে: একই header দুইবার, ভিন্ন সংজ্ঞা
# ফল: compile error, অথবা চুপচাপ ODR ভঙ্গ — undefined behavior
সমাধানের পথpackage manager আগেই জানায়
১. old_report-কে fmt 10-এ আপডেট করা   # সবচেয়ে ভালো
২. old_report-এর নতুন version খোঁজা
৩. vcpkg "overrides" / Conan "override" দিয়ে একটি version বাঁধা
   (তারপর পুরো টেস্ট চালানো বাধ্যতামূলক)
  1. Graph. আপনার প্রোগ্রাম সরাসরি দুটি লাইব্রেরি চায়। কিন্তু প্রত্যেকের নিজেরও নির্ভরতা আছে, আর সেগুলো একই জায়গায় গিয়ে মেলে — graph-টা দেখতে একটি হীরার (diamond) মতো, তাই নাম।
  2. প্রথম চাহিদা। spdlog fmt ১০ বা নতুন চায়। এখানে নির্ভরতাটা “public” — spdlog-এর header-এই fmt-এর টাইপ আছে, তাই আপনার কোডও সেই fmt দেখে।
  3. দ্বিতীয় চাহিদা। old_report বানানো হয়েছিল fmt ৮ দিয়ে, আর পরের major version-এ fmt-এর কিছু API বদলেছে। তাই সে ঠিক ৮.x চায়।
  4. সংঘাত। Python-এর মতো ভাষায় এটা “import error”; C++-এ আরও খারাপ। এক প্রোগ্রামে একই লাইব্রেরির দুই version link করলে একই নামের ফাংশন আর টাইপের দুই ভিন্ন সংজ্ঞা আসে — ODR ভঙ্গ, যার ফল undefined behavior (পর্ব ৪৪)। (fmt-এর নামের ভেতরে v10-এর মতো inline namespace থাকায় অনেক সময় link-error হয়ে ধরা পড়ে; সব লাইব্রেরি এই সতর্কতা নেয় না।)
  5. সমাধান। হাতে মেলালে এই সংঘাত ধরা পড়ে অনেক দেরিতে। Package manager পুরো graph আগেই হিসাব করে, আর সংঘাত হলে build শুরুর আগেই জানায়। তখন আপনি ঠিক করেন: নির্ভরতা আপডেট, বা সচেতনভাবে একটি version বাঁধা (override) — আর তারপর অবশ্যই পুরো টেস্ট।

৩ · বিকল্পের মানচিত্র — FetchContent, CPM, vcpkg, Conan

পর্ব ৪৯-এ আপনি CMake-এর তিনটি পথ দেখেছেন: find_package দিয়ে আগে থেকে বসানো লাইব্রেরি খোঁজা, FetchContent দিয়ে configure-এর সময় source নামানো, আর এক অনুচ্ছেদে vcpkg. এই পর্ব তৃতীয় পথটি গভীরে দেখবে। তার আগে পুরো মানচিত্রটা এক জায়গায়।

FetchContent-এর ওপর একটি জনপ্রিয় ছোট মোড়ক আছে — CPM.cmake. এটি একটি মাত্র .cmake ফাইল, যা প্রজেক্টে রাখলে এক লাইনে লাইব্রেরি আনা যায়, আর একই source একাধিক build ফোল্ডারে বারবার না নামিয়ে একটি cache-এ রাখা যায়:

include(cmake/CPM.cmake)
CPMAddPackage("gh:fmtlib/fmt#10.2.1")       # GitHub-এর fmtlib/fmt, tag 10.2.1
target_link_libraries(shop_core PRIVATE fmt::fmt)

FetchContent আর CPM দুটোই “source dependency”: লাইব্রেরি আপনার প্রজেক্টের অংশ হিসেবে, আপনার compiler আর flag দিয়েই build হয়। তাই ABI-র সমস্যা প্রায় থাকে না। কিন্তু প্রতিটি পরিষ্কার build-এ সব নির্ভরতা আবার compile হয়, আর লাইব্রেরিকে নিজেই CMake-বন্ধু হতে হয়। Boost, Qt বা OpenSSL-এর মতো বড় বা CMake-ছাড়া লাইব্রেরির জন্য এটা কষ্টকর।

পথকীভাবে আনেVersion বাঁধাWindowsভালো যেখানেদুর্বলতা
Source কপি (vendoring)Git-এ sourceযা কপি করেছেনহ্যাঁছোট header-only, অফলাইনআপডেট আর নিরাপত্তা-ঠিক হাতে
apt / brewসিস্টেমের binaryOS ঠিক করেনাশেখা, Linux সার্ভারএক version, মেশিনভেদে ভিন্ন
FetchContent / CPMconfigure-এ sourcetag বা commitহ্যাঁছোট লাইব্রেরি, টেস্ট-frameworkপ্রতি build-এ compile, CMake-বন্ধু লাইব্রেরি লাগে
vcpkgsource থেকে build, binary cachebaseline + versionখুব ভালোCMake প্রজেক্ট, Windows + Linuxএক registry-র ধাঁচ, port না থাকলে নিজে লিখতে হয়
Conan 2আগে-বানানো binary বা sourceversion + lockfileখুব ভালোবড় দল, নিজস্ব binary সার্ভারPython-ভিত্তিক টুল, শেখার বাঁক বেশি
মূল ধারণা

সব পথের একই লক্ষ্য: “কোন লাইব্রেরি, কোন version, কোন ABI” — এই তিন প্রশ্নের উত্তর মানুষের মাথা থেকে বের করে একটি ফাইলে রাখা, যা Git-এ থাকে আর যেকোনো মেশিনে একই ফল দেয়। এর নাম reproducible build. আর একটি সুসংবাদ: আপনার CMakeLists.txt প্রায় একই থাকে — find_package(fmt CONFIG REQUIRED) জানেই না fmt-টা apt, vcpkg না Conan থেকে এল।

৪ · vcpkg — একটি vcpkg.json আর একটি baseline

vcpkg Microsoft-এর তৈরি, open source (MIT লাইসেন্স), আর Windows, Linux, macOS তিনটিতেই চলে। এর registry-তে দুই হাজারের বেশি লাইব্রেরির রেসিপি আছে, প্রতিটিকে বলে একটি port. vcpkg মূলত source থেকে, আপনার মেশিনে, আপনার compiler দিয়ে লাইব্রেরি build করে — ABI মেলানোর সবচেয়ে নিশ্চিত উপায়।

এককালীন বসানো

git clone https://github.com/microsoft/vcpkg.git
cd vcpkg && ./bootstrap-vcpkg.sh       # Windows-এ: .\bootstrap-vcpkg.bat
export VCPKG_ROOT="$PWD"                 # vcpkg কোথায় আছে, সবাইকে জানানো

Visual Studio 2022-এর সাম্প্রতিক সংস্করণে vcpkg আগে থেকেই থাকে, আর GitHub Actions-এর তৈরি runner-গুলোতেও। তবে নিজের ক্লোন রাখলে কোন commit ব্যবহার হচ্ছে তা আপনার নিয়ন্ত্রণে থাকে।

Manifest mode — প্রজেক্টের নির্ভরতা প্রজেক্টের ফাইলে

পুরোনো দিনে vcpkg-এ vcpkg install fmt লিখে পুরো মেশিনের জন্য লাইব্রেরি বসানো হতো (classic mode). আধুনিক পদ্ধতি manifest mode: প্রজেক্টের মূল ফোল্ডারে একটি vcpkg.json, যা Git-এ থাকে। রাকিবের দোকানের প্রজেক্টের জন্য:

{
  "name": "shop",
  "version": "1.0.0",
  "dependencies": [
    "fmt",
    { "name": "spdlog", "version>=": "1.13.0" },
    "nlohmann-json",
    "gtest"
  ],
  "builtin-baseline": "0123456789abcdef0123456789abcdef01234567"
}

dependencies: প্রতিটি নাম একটি vcpkg port. লক্ষ করুন, port-এর নাম আর CMake-এর নাম সবসময় এক নয় — port nlohmann-json (হাইফেন), কিন্তু CMake-এ nlohmann_json (আন্ডারস্কোর); port gtest, কিন্তু CMake-এ GTest. AI প্রায়ই এখানে ভুল করে (অংশ ১০)।

"version>=": একটি version constraint — “অন্তত ১.১৩.০ চাই।” vcpkg-এ এটি “সর্বনিম্ন version”-এর অঙ্গীকার; vcpkg সবচেয়ে নতুনটা নয়, বরং সব শর্ত মেটায় এমন সবচেয়ে পুরোনো version বেছে নেয়। ফলে আজ আর ছয় মাস পরে একই ফল আসে।

builtin-baseline: vcpkg registry-র একটি নির্দিষ্ট Git commit-এর ৪০ অক্ষরের hash (এখানে উদাহরণের জন্য বানানো)। ওই commit-এ প্রতিটি port-এর যে version ছিল, সেটাই আপনার ডিফল্ট। version>= ব্যবহার করলে baseline বাধ্যতামূলক। আসল baseline বসাতে vcpkg-এর vcpkg x-update-baseline --add-initial-baseline কমান্ড আছে।

Baseline হলো vcpkg-এর সবচেয়ে চতুর ধারণা। এক commit মানে পুরো লাইব্রেরি-জগতের একটি “ছবি”, যেখানে সব port একসাথে পরীক্ষা করা হয়েছে। আপনি baseline না বদলালে কখনো version বদলাবে না; baseline আপডেট করা একটি সচেতন সিদ্ধান্ত, যা একটি Git commit হয়ে রিভিউতে যায়। কোনো নির্দিষ্ট লাইব্রেরি পুরোনো রাখতে চাইলে আছে overrides, আর লাইব্রেরির ঐচ্ছিক অংশ চাইলে features:

{
  "dependencies": [ "fmt", { "name": "curl", "features": [ "ssl" ] } ],
  "overrides": [ { "name": "fmt", "version": "10.2.1" } ],
  "builtin-baseline": "0123456789abcdef0123456789abcdef01234567"
}

CMake-কে vcpkg চেনানো — toolchain file আর preset

vcpkg CMake-এর সাথে জোড়া লাগে একটি toolchain file দিয়ে। পর্ব ৪৯-এর CMakePresets.json-এ এটা একবার লিখে রাখাই সবচেয়ে পরিষ্কার:

{
  "version": 6,
  "configurePresets": [
    {
      "name": "vcpkg-debug",
      "generator": "Ninja",
      "binaryDir": "build/debug",
      "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
      "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
    }
  ]
}

পুরোনো ধাঁচে একই কাজ কমান্ড-লাইনে: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake. এবার শুধু cmake --preset vcpkg-debug চালালে ভেতরে কী কী ঘটে, ধাপে ধাপে দেখুন:

  1. পড়াvcpkg.jsonকী লাগবে
  2. Version হিসাবbaselineপুরো graph
  3. Cache দেখাarchives/আগে বানানো?
  4. Buildx64-linuxtriplet অনুযায়ী
  5. বসানোvcpkg_installed/build ফোল্ডারে
  6. খোঁজা ও linkfmt::fmtfind_package
$ cmake --preset vcpkg-debugtoolchain file চালু হলো
-- Running vcpkg install
# প্রথম project() ডাকের শুরুতেই vcpkg.cmake দেখে: পাশে vcpkg.json আছে?
# আছে → manifest mode. চাহিদা: fmt, spdlog>=1.13.0, nlohmann-json, gtest
Version resolutionbaseline + শর্ত
The following packages will be built and installed:
    fmt:x64-linux@10.2.1          # spdlog-এর নির্ভরতা হিসেবেও
    gtest:x64-linux@1.14.0
    nlohmann-json:x64-linux@3.11.3
    spdlog:x64-linux@1.13.0
# সংখ্যাগুলো উদাহরণ — আপনার baseline ঠিক করবে
Binary cacheABI hash মিলিয়ে
Detecting compiler hash for triplet x64-linux...
Restored 2 package(s) from ~/.cache/vcpkg/archives
# fmt আর nlohmann-json আগের কোনো প্রজেক্টে একই সেটিংয়ে বানানো ছিল
# gtest আর spdlog নতুন — এগুলো build হবে
Port buildDebug + Release দুটোই
Building gtest:x64-linux@1.14.0...
-- Downloading https://github.com/google/googletest/archive/v1.14.0.tar.gz
-- SHA512 মিলেছে, extracting...
-- Configuring x64-linux-dbg
-- Configuring x64-linux-rel
-- Installing ... Stored binaries in 1 destinations
build/debug/vcpkg_installed/প্রজেক্ট-নিজস্ব
vcpkg_installed/x64-linux/
├── include/   fmt/  spdlog/  nlohmann/  gtest/
├── lib/       libfmt.a  libspdlog.a  libgtest.a ...
├── debug/lib/ libfmtd.a ...          # Debug কপি
└── share/     fmt/fmt-config.cmake ...  # CMake-এর জন্য
তারপর আপনার CMakeLists.txtস্বাভাবিক CMake
find_package(fmt CONFIG REQUIRED)    # share/fmt/fmt-config.cmake পায়
target_link_libraries(shop_core PRIVATE fmt::fmt)
-- Configuring done
-- Generating done
  1. Manifest পড়া। Toolchain file সবার আগে চলে — প্রথম project() ডাকের শুরুতে, compiler খোঁজারও আগে। সে দেখে প্রজেক্টের পাশে vcpkg.json আছে কি না। থাকলে configure-এর অংশ হিসেবেই vcpkg install চলে; আপনাকে আলাদা কমান্ড দিতে হয় না।
  2. Version হিসাব। vcpkg baseline থেকে প্রতিটি port-এর ডিফল্ট version নেয়, তারপর আপনার version>= আর overrides প্রয়োগ করে, আর transitive নির্ভরতাও (spdlog → fmt) যোগ করে। শর্তগুলো একসাথে মেটানো না গেলে এখানেই থামে — build শুরুর আগেই।
  3. Binary cache. প্রতিটি package-এর জন্য vcpkg একটি ABI hash বানায়: port-এর version, triplet, compiler, এমনকি নির্ভরতাগুলোর hash মিলিয়ে। একই hash-এর তৈরি জিনিস আগে কোথাও বানানো থাকলে compile না করে সেটাই ফিরিয়ে আনে। এর নাম binary caching, আর এটি ডিফল্টভাবে চালু।
  4. Port build. বাকিগুলোর জন্য vcpkg port-এর রেসিপি অনুযায়ী source নামায়, রেসিপিতে লেখা SHA512 hash মিলিয়ে দেখে কেউ ফাইল বদলেছে কি না, তারপর আপনার triplet অনুযায়ী Debug আর Release দুই কপি build করে।
  5. প্রজেক্টের ফোল্ডারে বসানো। Manifest mode-এ সব যায় build ফোল্ডারের vcpkg_installed/<triplet>/-এ। দুটি প্রজেক্ট দুই version ব্যবহার করলেও কেউ কাউকে বিরক্ত করে না — system package manager-এর “এক version” সমস্যা নেই।
  6. স্বাভাবিক CMake. এবার project()-এর বাকি কাজ আর বাকি CMakeLists.txt চলে। vcpkg-এর toolchain CMAKE_PREFIX_PATH-এ ওই ফোল্ডার যোগ করে রেখেছে, তাই find_package config ফাইল খুঁজে পায় আর imported target দেয়। প্রথমবার কয়েক মিনিট লাগতে পারে; পরের বার cache থেকে কয়েক সেকেন্ড।

Triplet — “কোন মাপের বোতাম”

Triplet হলো vcpkg-এর ভাষায় ABI-র বর্ণনা: আর্কিটেকচার, প্ল্যাটফর্ম, আর লাইব্রেরি static না dynamic. নামটা পুরোনো “target triple” থেকে এসেছে। প্রচলিত কয়েকটি:

Tripletলাইব্রেরিমন্তব্য
x64-windowsDLL (shared), dynamic CRTWindows-এ সাধারণ ডিফল্ট
x64-windows-staticstatic, static CRT (/MT)এক .exe-তে সব; আপনার প্রজেক্টকেও /MT হতে হবে
x64-windows-static-mdstatic, dynamic CRT (/MD)অনেক দলের প্রিয় মাঝামাঝি পথ
x64-linuxstaticLinux-এ ডিফল্ট
arm64-osxstaticApple Silicon Mac
x64-mingw-dynamicDLL, MinGWWindows-এ GCC; community triplet

Triplet বাছতে CMake-এ VCPKG_TARGET_TRIPLET variable দিন (preset-এর cacheVariables-এ)। অংশ ১-এর /MT বনাম /MD সমস্যাটা মনে আছে? x64-windows-static বাছলে আপনার নিজের প্রোগ্রামকেও static CRT-তে build করতে হয় — CMake-এ CMAKE_MSVC_RUNTIME_LIBRARY দিয়ে। Triplet বদলানো মানে ABI বদলানো; তাই এটা দলের সবার জন্য preset-এ লিখে রাখুন, প্রত্যেকের ইচ্ছামতো নয়।

৫ · দলের জন্য vcpkg — binary cache, overlay port আর নিজস্ব registry

একা কাজ করলে উপরের টুকুই যথেষ্ট। দল আর CI এলে তিনটি প্রশ্ন ওঠে: প্রতিটি মেশিনে কি একই লাইব্রেরি আবার compile হবে? কোম্পানির নিজস্ব লাইব্রেরি কোথায় থাকবে? আর public registry-র কোনো port-এ ছোট বদল লাগলে কী করব?

Binary cache ভাগ করা

vcpkg ডিফল্টভাবে নিজের মেশিনে একটি ফোল্ডারে cache রাখে (Linux-এ সাধারণত ~/.cache/vcpkg/archives, Windows-এ %LOCALAPPDATA%\vcpkg\archives). দলের জন্য VCPKG_BINARY_SOURCES environment variable দিয়ে একটি ভাগ করা জায়গা দেওয়া যায় — একটি network ফোল্ডার, একটি NuGet feed, বা cloud storage. Cache-এর চাবি সেই ABI hash, তাই ভুল ABI-র binary ভুল মেশিনে যায় না।

# নিজের network ফোল্ডারকে cache বানানো (পড়া ও লেখা দুটোই)
export VCPKG_BINARY_SOURCES="clear;files,/mnt/team-cache/vcpkg,readwrite"

Overlay port — নিজের রেসিপি

কোম্পানির নিজের লাইব্রেরি, ধরা যাক bkash-sdk-internal, public registry-তে নেই। বা public fmt port-এ একটি patch দরকার। তখন প্রজেক্টে একটি ports/ ফোল্ডারে নিজের port লিখুন — প্রতিটি port মানে একটি ফোল্ডার, তাতে একটি vcpkg.json আর একটি portfile.cmake (কোথা থেকে source আসবে, কীভাবে build হবে)। এর নাম overlay port: একই নামের public port-কে এটি “ঢেকে” দেয়।

{
  "default-registry": {
    "kind": "git",
    "repository": "https://github.com/microsoft/vcpkg",
    "baseline": "0123456789abcdef0123456789abcdef01234567"
  },
  "overlay-ports": [ "./ports" ]
}

অনেকগুলো প্রজেক্টে একই অভ্যন্তরীণ লাইব্রেরি লাগলে overlay-র বদলে কোম্পানির একটি নিজস্ব Git registry বানানো ভালো — তাতে ports আর version-এর ইতিহাস থাকে, আর vcpkg-configuration.json-এর registries অংশে লিখে দিলে নির্দিষ্ট package-গুলো সেখান থেকে আসে। লক্ষ করুন, এই ফাইলেও baseline আছে; নতুন প্রজেক্টে vcpkg new --application চালালে vcpkg baseline-টা এখানেই লিখে দেয়।

৬ · Conan 2 — profile, conanfile আর --build=missing

Conan-এর দর্শন একটু আলাদা। vcpkg-এর মূল ভাবনা “আপনার মেশিনে source থেকে build, সাথে cache”; Conan-এর মূল ভাবনা “প্রতিটি সেটিংয়ের জন্য আলাদা binary package, সার্ভারে জমা” — না পেলে তখন build. Conan Python-এ লেখা, তাই বসানো সহজ: pip install conan. এখানে আমরা শুধু Conan 2 দেখব (২০২৩-এ এসেছে); Conan 1-এর অনেক কমান্ড আর ফাইল এতে চলে না।

Profile — আপনার ABI এক ফাইলে

প্রথমবার conan profile detect চালালে Conan আপনার compiler খুঁজে একটি ডিফল্ট profile লেখে। এটি আসলে অংশ ১-এর টেবিলটিরই লিখিত রূপ:

[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu17
compiler.libcxx=libstdc++11
compiler.version=13
os=Linux

compiler.libcxx=libstdc++11 লক্ষ করুন — এটাই সেই নতুন std::string ABI (অংশ ১-এর runnable কোডের new string ABI = 1). দরকার হলে নিজের নামে আরও profile বানান, যেমন debug বা windows-msvc. compiler.cppstd-এ আপনার প্রজেক্টের standard দিন (C++23-এর জন্য 23 বা gnu23, যদি আপনার Conan সংস্করণ আর compiler সমর্থন করে)।

conanfile — কী লাগবে

সরল প্রজেক্টে একটি conanfile.txt:

[requires]
fmt/10.2.1
spdlog/1.13.0
nlohmann_json/3.11.3

[test_requires]
gtest/1.14.0

[generators]
CMakeDeps
CMakeToolchain

[layout]
cmake_layout

Conan-এ version সাধারণত হুবহু লেখা হয় (fmt/10.2.1); version range-ও আছে (fmt/[>=10 <11]). Package-এর নামও vcpkg থেকে আলাদা হতে পারে: Conan-এ nlohmann_json, vcpkg-এ nlohmann-json. শর্ত বা যুক্তি লাগলে (যেমন “শুধু Windows-এ এই লাইব্রেরি”) Python-এ conanfile.py:

from conan import ConanFile
from conan.tools.cmake import cmake_layout

class ShopRecipe(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires("fmt/10.2.1")
        self.requires("spdlog/1.13.0")
        if self.settings.os == "Windows":
            self.requires("openssl/[>=3 <4]")   # Linux-এ সিস্টেমের OpenSSL, Windows-এ Conan থেকে

    def build_requirements(self):
        self.test_requires("gtest/1.14.0")

    def layout(self):
        cmake_layout(self)

প্রথম লাইনটি খেয়াল করুন: Conan 2-এ from conan import ConanFile. Conan 1-এ ছিল from conans import ConanFile (শেষে “s”) — AI প্রায়ই এটা মিশিয়ে ফেলে।

Install থেকে build — পুরো প্রবাহ

conan profile detect --force               # একবার: ডিফল্ট profile
conan install . --build=missing            # নির্ভরতা আনা/বানানো + CMake ফাইল লেখা
cmake --preset conan-release               # configure (Conan-এর বানানো preset)
cmake --build --preset conan-release       # build
  1. ইনপুটprofile + conanfileকে আমি, কী চাই
  2. Graphconan installversion হিসাব
  3. Binary খোঁজাpackage_idremote-এ আছে?
  4. না থাকলে--build=missingsource থেকে
  5. Generatorconan_toolchain.cmakeCMake-এর ফাইল
  6. CMakeconan-releasebuild
দুই ইনপুটhost profile + চাহিদা
profile : os=Linux, compiler=gcc 13, libcxx=libstdc++11, build_type=Release
conanfile.txt : fmt/10.2.1, spdlog/1.13.0, nlohmann_json/3.11.3
                gtest/1.14.0 (শুধু টেস্টের জন্য)
$ conan install . --build=missingসংক্ষেপে
======== Computing dependency graph ========
Requirements
    fmt/10.2.1#...         - Downloaded (conancenter)
    spdlog/1.13.0#...      - Downloaded (conancenter)
# spdlog-এর নিজের চাহিদা fmt — একই fmt/10.2.1-এ মিলেছে
Computing necessary packagesসেটিং → hash
fmt/10.2.1     package_id a1b2…  # gcc13 + Release + libstdc++11-এর hash
               → conancenter-এ আছে → Download
spdlog/1.13.0  package_id c3d4…
               → এই সেটিংয়ের binary নেই → Missing
--build=missing ছাড়া হলেবনাম সাথে
ERROR: Missing prebuilt package for 'spdlog/1.13.0'
# --build=missing দিলে:
spdlog/1.13.0: Building from source...
spdlog/1.13.0: Package c3d4… created  # লোকাল cache-এ জমা
build/Release/generators/Conan-এর লেখা ফাইল
conan_toolchain.cmake       # CMakeToolchain: compiler, flag, prefix path
fmt-config.cmake            # CMakeDeps: find_package(fmt) এর জন্য
spdlog-config.cmake
nlohmann_json-config.cmake
CMakePresets.json           # + প্রজেক্টে CMakeUserPresets.json
$ cmake --preset conan-releaseস্বাভাবিক CMake
-- Using Conan toolchain: .../conan_toolchain.cmake
-- Conan: Target declared 'fmt::fmt'
-- Conan: Target declared 'spdlog::spdlog'
-- Configuring done
# $ cmake --build --preset conan-release
  1. দুই ইনপুট। Profile বলে “আমি কে” — os, compiler, standard library, build type. Conanfile বলে “আমার কী লাগবে।” Cross-compile করলে “build” আর “host” দুটি আলাদা profile-ও দেওয়া যায়, যেমন x64 PC-তে বসে ARM বোর্ডের জন্য।
  2. Dependency graph. Conan প্রতিটি চাহিদার রেসিপি (ConanCenter বা আপনার দেওয়া remote থেকে) নামিয়ে transitive নির্ভরতাসহ পুরো graph বানায়। দুই জায়গা থেকে একই লাইব্রেরির দুই version চাইলে এখানেই সংঘাতের error দেয়।
  3. Package ID. এটাই Conan-এর হৃদয়। প্রতিটি package-এর জন্য সে profile-এর সংশ্লিষ্ট সেটিং আর option থেকে একটি hash বানায় — package_id. একই রেসিপি, ভিন্ন compiler বা build type মানে ভিন্ন package_id, ভিন্ন binary. Remote-এ সেই ID-র binary থাকলে শুধু নামায়।
  4. না থাকলে build. আপনার সেটিংয়ের binary কোথাও না থাকলে Conan থেমে যায় — “Missing prebuilt package.” --build=missing মানে “যেগুলো নেই শুধু সেগুলো source থেকে বানাও।” বানানো binary লোকাল cache-এ (~/.conan2) জমা থাকে, পরের বার আর বানাতে হয় না।
  5. Generator. Install-এর শেষে generator-গুলো চলে। CMakeDeps প্রতিটি package-এর জন্য <name>-config.cmake লেখে, যাতে find_package কাজ করে; CMakeToolchain লেখে conan_toolchain.cmake (profile-এর compiler আর flag CMake-এ পৌঁছায়) আর একটি preset ফাইল। cmake_layout থাকায় সব যায় build/Release/generators/-এ।
  6. স্বাভাবিক CMake. Conan প্রজেক্টে একটি CMakeUserPresets.json রাখে, তাই cmake --preset conan-release নিজেই toolchain খুঁজে নেয়। Visual Studio-র মতো multi-config generator-এ preset-এর নাম সাধারণত conan-default. আপনার CMakeLists.txt-এ Conan-এর কোনো উল্লেখ নেই — শুধু find_package.

Lockfile আর remote — দলের অভ্যাস

Conanfile-এ হুবহু version লিখলেও একটি ফাঁক থাকে: একই version-এর রেসিপিও পরে সংশোধিত হতে পারে (Conan-এর ভাষায় নতুন “revision”), আর transitive নির্ভরতা range দিয়ে লেখা থাকলে আজ আর কাল ভিন্ন ফল আসতে পারে। তাই দলের অ্যাপ্লিকেশনের জন্য একটি lockfile বানিয়ে Git-এ রাখুন:

conan lock create .                          # conan.lock: সব version আর revision বাঁধা
conan install . --lockfile=conan.lock --build=missing

Conan package আসে remote থেকে — ডিফল্ট remote হলো ConanCenter, যেখানে কমিউনিটির রক্ষণাবেক্ষণ করা হাজারো রেসিপি আছে। অনেক কোম্পানি নিজের একটি remote চালায় (যেমন JFrog Artifactory-তে, বা open source conan_server), যেখানে তিনটি জিনিস থাকে: অভ্যন্তরীণ লাইব্রেরি, CI-তে আগে-বানানো binary, আর ConanCenter থেকে যাচাই করে আনা রেসিপির কপি।

conan remote add mycompany https://conan.example.com/artifactory/api/conan/conan-local
conan upload "shop_core/*" -r=mycompany       # নিজের package তোলা
conan remote disable conancenter              # কঠোর নীতি: শুধু যাচাই করা remote

ঢাকার একটি সফটওয়্যার কোম্পানির ৪০ জন ডেভেলপার প্রতিদিন সকালে যদি Boost নিজে compile করেন, প্রতিজন আধা ঘণ্টা — দিনে ২০ ঘণ্টা নষ্ট। নিজস্ব Conan remote থাকলে CI রাতে একবার প্রতিটি সেটিংয়ের binary বানিয়ে রাখে, সকালে সবাই শুধু নামান। আর load-shedding-এর দিনে বিদেশের সার্ভারে পৌঁছানো না গেলেও অফিসের সার্ভার থেকে build চলে।

৭ · CMake-এ লাইব্রেরি ব্যবহার — fmt, spdlog, nlohmann_json, GoogleTest, Boost

vcpkg বা Conan যেটাই হোক, আপনার CMakeLists.txt-এর কাজ একই: find_package দিয়ে খোঁজা, target_link_libraries দিয়ে জোড়া। ঝামেলা শুধু নামে — তিন জায়গায় তিন রকম নাম থাকতে পারে। নিচের টেবিল প্রচলিত লাইব্রেরিগুলোর জন্য, তবু নতুন version-এ কিছু বদলালে লাইব্রেরির নিজের ডকুমেন্টেশন বা vcpkg install-এর শেষে ছাপা “usage” লেখাটিই চূড়ান্ত:

লাইব্রেরিvcpkg portConanfind_packageTarget
fmtfmtfmt/…fmt CONFIGfmt::fmt (বা fmt::fmt-header-only)
spdlogspdlogspdlog/…spdlog CONFIGspdlog::spdlog
nlohmann/jsonnlohmann-jsonnlohmann_json/…nlohmann_json CONFIGnlohmann_json::nlohmann_json
GoogleTestgtestgtest/…GTest CONFIGGTest::gtest, GTest::gtest_main
Boostboost-json, boost-asio… (বা সব একসাথে boost)boost/…Boost CONFIG COMPONENTS jsonBoost::json; শুধু header: Boost::headers

বাস্তব উদাহরণ — ক্যাপস্টোনে fmt, spdlog আর config ফাইল

পর্ব ৫৪-এর ক্যাপস্টোন প্রজেক্টে আপনি একটি আসল C++ সিস্টেম বানাবেন। ধরা যাক, তাতে তিনটি জিনিস লাগবে: সুন্দর ফরম্যাটে আউটপুট (fmt), ফাইলে log লেখা (spdlog), আর একটি config.json থেকে সেটিং পড়া (nlohmann_json). সাথে পর্ব ৫১-এর টেস্টের জন্য GoogleTest. উপরের vcpkg.json বা conanfile.txt থাকলে CMakeLists.txt-এর প্রাসঙ্গিক অংশ:

find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)

add_library(shop_core STATIC src/inventory.cpp src/config.cpp)
target_link_libraries(shop_core
    PUBLIC  fmt::fmt                         # public header-এ fmt-এর টাইপ আছে
    PRIVATE spdlog::spdlog nlohmann_json::nlohmann_json)

find_package(GTest CONFIG REQUIRED)
add_executable(shop_tests tests/config_test.cpp)
target_link_libraries(shop_tests PRIVATE shop_core GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(shop_tests)

প্রতিটি নির্ভরতায় পর্ব ৪৯-এর PUBLIC/PRIVATE প্রশ্নটি আবার করুন। spdlog আর JSON শুধু .cpp-এর ভেতরে ব্যবহার হয়, তাই PRIVATE — shop_core-এর ব্যবহারকারী এগুলো সম্পর্কে কিছুই জানে না, পরে JSON লাইব্রেরি বদলালেও তাদের কিছু বদলাতে হবে না। CONFIG শব্দটি বলে “লাইব্রেরির নিজের config ফাইল খোঁজো”, যা vcpkg আর Conan দুজনেই সরবরাহ করে। আর পর্ব ৪৯-এ GoogleTest FetchContent দিয়ে এনেছিলেন; এখানে package manager থেকে — target-এর নাম একই, তাই টেস্টের কোড একটুও বদলায় না।

একটি লক্ষণীয় ব্যাপার: পর্ব ০৭-এ আপনি std::format আর std::println ব্যবহার করেছেন। এগুলো আসলে fmt লাইব্রেরিরই standard-রূপ — Victor Zverovich-এর fmt-এর নকশা প্রস্তাব আকারে C++20-এ std::format হয়েছে, আর C++23-এ std::print. অর্থাৎ নতুন compiler থাকলে সরল ফরম্যাটিংয়ের জন্য আর বাইরের নির্ভরতা লাগে না; পুরোনো compiler সমর্থন করতে হলে বা fmt-এর বাড়তি সুবিধা (যেমন রঙিন আউটপুট) লাগলে fmt. নিচের প্রোগ্রাম std::format দিয়েই একটি package manager-এর ছোট্ট কাজ করে দেখায় — কোন version একটি version>= শর্ত মেটায়:

#include <compare>
#include <format>
#include <print>
#include <string>
#include <vector>
struct Ver {
    int major, minor, patch;
    auto operator<=>(const Ver&) const = default;   // major, then minor, then patch
};
std::string str(Ver v) { return std::format("{}.{}.{}", v.major, v.minor, v.patch); }
int main() {
    const Ver need{10, 1, 0};                         // "version>=": "10.1.0"
    const std::vector<Ver> registry{{9, 1, 0}, {10, 1, 0}, {10, 2, 1}, {11, 0, 2}};
    std::println("{:<8} {:<4} {}", "version", ">=", "same major");
    for (Ver v : registry)
        std::println("{:<8} {:<4} {}", str(v), v >= need ? "yes" : "no",
                     v.major == need.major ? "yes" : "no");
    for (Ver v : registry)
        if (v >= need) { std::println("minimum that satisfies: {}", str(v)); break; }
}

operator<=>-কে = default করায় (পর্ব ২৩) তুলনা হয় সদস্যদের ক্রমে — আগে major, সমান হলে minor, তারপর patch. তাই 10.2.1 >= 10.1.0 সত্য, 9.1.0 মিথ্যা। দুটি শিক্ষা আছে। প্রথম, 11.0.2 শর্ত মেটায়, কিন্তু major আলাদা — semantic versioning-এ এর মানে “API ভাঙতে পারে।” >= শর্ত একা এই বিপদ থেকে বাঁচায় না। দ্বিতীয়, শেষ লাইন vcpkg-এর নিয়মটি দেখায়: শর্ত মেটায় এমন সবচেয়ে ছোট version, অর্থাৎ 10.1.0 — সবচেয়ে নতুন 11.0.2 নয়। ফলে নতুন রিলিজ এলেও আপনার build নিজে নিজে বদলায় না।

৮ · Version, CI আর লাইসেন্স — দীর্ঘ জীবনের অভ্যাস

Semantic versioning আর version বাঁধা

বেশিরভাগ লাইব্রেরি MAJOR.MINOR.PATCH নিয়ম মানার চেষ্টা করে: PATCH মানে শুধু বাগ-ঠিক, MINOR মানে নতুন সুবিধা কিন্তু পুরোনো কোড চলবে, MAJOR মানে API ভাঙতে পারে। C++-এ একটি বাড়তি সূক্ষ্মতা আছে — API (source) সামঞ্জস্য আর ABI (binary) সামঞ্জস্য আলাদা। একটি struct-এ নতুন private সদস্য যোগ করলে আপনার কোড একটুও না বদলে compile হয়, কিন্তু struct-এর আকার বদলেছে; পুরোনো binary-র সাথে মেশালে crash. তাই C++-এ একটি minor আপডেটের পরেও সব কিছু আবার build করা নিরাপদ অভ্যাস — package manager ঠিক এটাই করে।

নিয়মটা সহজ: অ্যাপ্লিকেশনের নির্ভরতা বেঁধে রাখুন (version pinning) — vcpkg-এ baseline (আর দরকারে overrides), Conan-এ হুবহু version আর lockfile, FetchContent-এ tag বা commit hash. আপডেট হবে পরিকল্পিতভাবে: একটি আলাদা branch-এ baseline বদলানো, পুরো টেস্ট চালানো, তারপর merge. আর আপনি নিজে লাইব্রেরি লিখলে উল্টো নীতি — খুব সরু version চাইবেন না, যাতে ব্যবহারকারীর diamond সংঘাত না হয়।

CI-তে package manager

পর্ব ৫১-এ টেস্টের সাথে continuous integration-এর কথাও আসবে। এখানে শুধু package manager-এর অংশটুকু: CI মেশিন প্রতিবার নতুন, তাই cache না রাখলে প্রতিটি push-এ সব লাইব্রেরি নতুন করে compile হয়। GitHub Actions-এ vcpkg-এর একটি সরল রূপ:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          path: ~/.cache/vcpkg/archives
          key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }}
      - run: |
          export VCPKG_ROOT="$VCPKG_INSTALLATION_ROOT"
          cmake --preset vcpkg-debug
          cmake --build build/debug
          ctest --test-dir build/debug --output-on-failure

Cache-এর চাবিতে vcpkg.json-এর hash রাখায় নির্ভরতা বদলালে নতুন cache তৈরি হয়, না বদলালে আগেরটা ফিরে আসে। বাস্তবে অনেক দল vcpkg-কে নিজের repository-তে Git submodule হিসেবে রাখে, যাতে runner-এর আগে-বসানো vcpkg-এর version-এর ওপর নির্ভর করতে না হয়। Conan-এর জন্য একই ধারণা: ~/.conan2-এর package ফোল্ডার cache করা, বা আরও ভালো, কোম্পানির নিজস্ব remote থেকে binary নামানো।

লাইসেন্স — কোড বিনামূল্যে, শর্ত বিনামূল্যে নয়

প্রতিটি নির্ভরতা একটি software license নিয়ে আসে, আর আপনার প্রোগ্রাম বিতরণ করলে সেই শর্ত মানতে হয়। এখানে সাধারণ ধারণা দেওয়া হলো — এটি আইনি পরামর্শ নয়; বাণিজ্যিক পণ্যের জন্য প্রতিষ্ঠানের আইন-বিভাগের সাথে কথা বলুন।

ধরনউদাহরণসাধারণ অর্থ
PermissiveMIT (fmt, spdlog, nlohmann/json), BSD-3-Clause (GoogleTest), Boost Software License, Apache-2.0বাণিজ্যিক বা বন্ধ-source পণ্যেও ব্যবহার করা যায়; সাধারণত copyright আর লাইসেন্স-লেখা সাথে রাখতে হয়
Weak copyleftLGPLলাইব্রেরিতে বদল করলে সেটা প্রকাশ করতে হয়; dynamic link করা তুলনামূলক সহজ, static link-এ বাড়তি শর্ত আসতে পারে
Strong copyleftGPLGPL কোড যুক্ত প্রোগ্রাম বিতরণ করলে সাধারণত পুরো প্রোগ্রামের source একই শর্তে দিতে হয়

vcpkg প্রতিটি port-এর লাইসেন্স-লেখা vcpkg_installed/<triplet>/share/<port>/copyright-এ রাখে, আর Conan-এর conan graph info . প্রতিটি package-এর license দেখায়। নতুন নির্ভরতা যোগ করার আগে লাইসেন্স দেখা যেন version দেখার মতোই অভ্যাস হয়। বড় প্রতিষ্ঠানে এখন প্রতিটি পণ্যের সাথে সব নির্ভরতার একটি তালিকা রাখা হয় — SBOM (Software Bill of Materials); vcpkg-ও SPDX ফরম্যাটে এমন তথ্য লিখতে পারে।

৯ · নিরাপত্তা আর ভবিষ্যৎ — supply chain আর modules

প্রতিটি নির্ভরতা মানে আপনার প্রোগ্রামে অন্য কারও লেখা কোড, যা আপনার ব্যবহারকারীর মেশিনে আপনার নামে চলে। আক্রমণকারীরা তাই সরাসরি আপনাকে না আক্রমণ করে আপনার কোনো নির্ভরতাকে নিশানা করতে পারে — এর নাম supply-chain attack. ২০২৪ সালে Linux-এর বহুল ব্যবহৃত C লাইব্রেরি xz utils-এ এমন একটি backdoor ধরা পড়েছিল, যা কয়েক বছর ধরে বিশ্বাস অর্জন করা একজন “রক্ষণাবেক্ষণকারী” ঢুকিয়েছিলেন। সৌভাগ্যক্রমে সেটা ব্যাপকভাবে ছড়ানোর আগেই একজন প্রকৌশলী ছোট একটি গতির পার্থক্য দেখে ধরে ফেলেন। C আর C++-এর জগৎও তাই নিরাপদ নয়। আত্মরক্ষার অভ্যাসগুলো:

ভবিষ্যৎ — modules আর import std;

পর্ব ৪৩-এ C++20 modules দেখেছেন, আর C++23-এ এসেছে import std; — পুরো standard library এক লাইনে। Packaging-এর জন্য এর মানে বড়, কিন্তু এখনো পরিবর্তনশীল। Module compile হয়ে যে BMI ফাইল বানায়, তা compiler-নির্দিষ্ট, এমনকি compiler-এর flag-নির্দিষ্ট — অর্থাৎ header-এর চেয়েও কঠোর “ABI”. তাই লাইব্রেরিগুলো module-এর source (interface ফাইল) বিতরণ করে, আর আপনার build system সেটা আপনার compiler দিয়ে compile করে। CMake এ জন্য নতুন সুবিধা যোগ করেছে, আর vcpkg ও Conan module-যুক্ত লাইব্রেরির সমর্থন ধাপে ধাপে বাড়াচ্ছে। এই পর্ব লেখার সময় বড় প্রজেক্টে সাধারণ header আর find_package-ই মূলধারা; modules-এর দিকে নজর রাখুন, কিন্তু আজকের প্রজেক্ট সেটির ওপর দাঁড় করাবেন না।

অন্য ভাষায় একই ধারণা

Python-এ pip আর requirements.txt বা pyproject.toml, সাথে lock করার টুল (Python-এর packaging পর্ব); Java-তে Maven-এর pom.xml বা Gradle (Java-র Maven ও Gradle পর্ব); JavaScript-এ package.json আর package-lock.json; Rust-এ Cargo.toml আর Cargo.lock. ধারণা একই — manifest (কী চাই), resolution (কোন version), lock (ঠিক কী পেলাম)। C++-এ বাড়তি স্তর একটাই: ABI, যার জন্য triplet বা profile লাগে।

পরীক্ষা ও ইন্টারভিউতে

প্র: ABI কী, আর C++ লাইব্রেরির ক্ষেত্রে কেন গুরুত্বপূর্ণ? উ: compile করা কোড কীভাবে একে অন্যের সাথে জোড়া লাগে তার binary-স্তরের চুক্তি — name mangling, টাইপের গঠন, calling convention; compiler, standard library বা build type না মিললে লাইব্রেরি link হয় না বা ভুল চলে।

প্র: vcpkg-এর builtin-baseline কী কাজ করে? উ: registry-র একটি নির্দিষ্ট commit-এ সব port-এর version বেঁধে দেয়, ফলে যেকোনো মেশিনে, যেকোনো দিনে একই version আসে।

প্র: Conan-এ --build=missing মানে কী? উ: আপনার profile-এর সাথে মেলে এমন আগে-বানানো binary যেগুলোর নেই, শুধু সেগুলো source থেকে build করা।

প্র: header-only লাইব্রেরির সুবিধা ও অসুবিধা? উ: আলাদা build বা ABI-র ঝামেলা নেই; কিন্তু প্রতিটি ব্যবহারকারী ফাইলে আবার compile হয়, তাই compile সময় বাড়ে।

প্র: diamond dependency কী? উ: দুটি নির্ভরতা একই তৃতীয় লাইব্রেরির ভিন্ন version চাইলে যে সংঘাত হয়; C++-এ দুটো একসাথে link করলে ODR ভঙ্গ হতে পারে।

১০ · AI-এর যুগে — বানানো package-নাম আর মেশানো Conan

AI-কে “আমার প্রজেক্টে vcpkg দিয়ে JSON লাইব্রেরি যোগ করে দাও” বললে সে আত্মবিশ্বাসের সাথে ফাইল লিখে দেয়। কিন্তু build ফাইল এমন জায়গা, যেখানে একটি অক্ষরের ভুলও configure থামিয়ে দেয় — আর AI-এর ভুলগুলো এখানে খুব নির্দিষ্ট ধরনের:

AI যা লেখেসমস্যাযাচাইয়ের উপায়
vcpkg-এ "nlohmann_json", বা CMake-এ nlohmann-json::nlohmann-jsonport আর CMake-নাম গুলিয়ে ফেলাvcpkg.io-তে port খোঁজা; install-এর শেষের usage লেখা পড়া
নেই এমন version, যেমন fmt/12.5.0বানানো version — install ব্যর্থConanCenter বা vcpkg-এর version তালিকায় মিলিয়ে নেওয়া
version>= আছে, builtin-baseline নেইvcpkg error দেয়, বা version বাঁধা থাকে নাbaseline আছে কি না দেখা
Conan 2 প্রজেক্টে conanbuildinfo.cmake, conan_basic_setup(), CONAN_PKG::fmtConan 1-এর জিনিস; Conan 2-এ নেইConan 2-এর generator: শুধু CMakeDeps + CMakeToolchain
from conans import ConanFile, [generators] cmakeConan 1-এর syntaxfrom conan import ConanFile
বানানো target, যেমন GTest::Main বা spdlog::coreconfigure-এ “target not found”লাইব্রেরির ডকুমেন্টেশন বা config ফাইলে আসল নাম
সব নির্ভরতা PUBLIC; lockfile বা baseline Git-এ না রাখার পরামর্শনির্ভরতা ছড়ায়; build reproducible থাকে নাPRIVATE ডিফল্ট; lock ফাইল commit করা

AI-কে কাজ দিন, কিন্তু অনুমান করার সুযোগ কমিয়ে দিন — আপনার toolchain আর টুলের version স্পষ্ট বলে, আর যেখানে সে নিশ্চিত নয় সেটা বলতে বাধ্য করে। নিচের prompt কপি করে নিজের তথ্য বসান:

তুমি একজন অভিজ্ঞ C++ build engineer. আমার CMake প্রজেক্টে vcpkg manifest mode দিয়ে নির্ভরতা যোগ করতে সাহায্য করো। আমার পরিবেশ: - CMake 3.28, Ninja, Windows-এ MSVC 2022 আর Linux-এ GCC 13 - লাগবে: fmt, spdlog, nlohmann/json, আর টেস্টের জন্য GoogleTest নিয়ম: ১. vcpkg.json লিখবে; builtin-baseline-এর জায়গায় "BASELINE" লিখে রাখবে, কোনো hash বানাবে না ২. প্রতিটি লাইব্রেরির জন্য আলাদা করে বলবে: vcpkg port-এর নাম, find_package-এর নাম, CMake target-এর নাম ৩. CMakePresets.json-এ toolchainFile দিয়ে vcpkg যুক্ত করবে; Windows-এর জন্য triplet x64-windows-static-md ৪. target_link_libraries-এ প্রতিটি নির্ভরতার পাশে PUBLIC না PRIVATE, আর এক লাইনে কারণ ৫. কোনো version সংখ্যা লিখলে বলবে সেটা কোথায় যাচাই করা যায়; নিশ্চিত না হলে "নিশ্চিত নই" লিখবে ৬. Conan-এর কিছু বললে শুধু Conan 2-এর syntax, Conan 1 মেশাবে না

AI-এর লেখা build ফাইল যাচাইয়ের চেকলিস্ট

শেষ কথা: package-নাম, version আর target-নাম মুখস্থ তথ্য, আর তা প্রায়ই বদলায়। তাই যুক্তি AI-এর কাছ থেকে নিন, তথ্য registry থেকে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১Rust-এর cargo-ও তো compile করা ভাষার package manager. তাহলে C++ কেন কখনো একটি সর্বজনীন pip বা npm পেল না — সমস্যাটা ISO standard-এ, নাকি ABI-তে?

দুটোতেই, তবে ভিন্নভাবে। প্রথমে standard-এর দিক। C++-এর ISO standard (WG21-এর লেখা) একটি ভাষা আর তার library বর্ণনা করে — “একটি প্রোগ্রাম কী অর্থ বহন করে।” কীভাবে ফাইল compile হবে, লাইব্রেরি কোথায় থাকবে, কোন ফোল্ডার-বিন্যাস — এসব standard-এর পরিধির বাইরে, ঠিক যেমন C-র standard-এ নেই। WG21-এর ভেতরে SG15 নামের tooling study group বহু বছর ধরে এ নিয়ে কাজ করছে — modules-এর জন্য build system আর compiler-এর তথ্য বিনিময়, লাইব্রেরির নিজের বর্ণনার সাধারণ ফরম্যাট। কিন্তু কমিটি কোনো একটি টুলকে “অফিসিয়াল” ঘোষণা করেনি, আর করলেও বিদ্যমান কোটি কোটি লাইন কোড রাতারাতি সেখানে যেত না।

এবার Rust-এর তুলনা, যা আসল শিক্ষা দেয়। Rust-এরও কোনো স্থির ABI নেই — এক compiler version-এর বানানো লাইব্রেরি অন্য version-এ ব্যবহারের নিশ্চয়তা নেই। তবুও cargo চলে, কারণ তিনটি সিদ্ধান্ত: (১) cargo প্রায় সবকিছু source থেকে, আপনার মেশিনে, আপনার compiler দিয়ে build করে; (২) কার্যত একটিই compiler (rustc) আর একটিই standard library; (৩) cargo ভাষার জন্মের সময় থেকেই ছিল (Rust ১.০, ২০১৫), তাই প্রতিটি লাইব্রেরি একই build-ভাষায় লেখা। C++-এ তিনটিই উল্টো: তিনটি বড় compiler, তিনটি standard library, আর কয়েক ডজন build system — CMake, Meson, Autotools, Bazel, MSBuild, হাতে লেখা Makefile.

লক্ষ করুন, vcpkg আসলে cargo-র প্রথম সিদ্ধান্তটিই নিয়েছে: source থেকে, আপনার মেশিনে। তার প্রতিটি port হলো একটি লাইব্রেরিকে তার নিজের build system দিয়ে build করে সাধারণ আকারে সাজানোর রেসিপি। Conan নিয়েছে অন্য পথ: ABI-কে স্পষ্ট করে (settings → package_id) প্রতিটি সম্ভাব্য ABI-র জন্য আলাদা binary রাখা। Python-ও C/C++ কোডওয়ালা লাইব্রেরিতে একই সমস্যায় পড়েছে — তাই wheel ফাইলের নামে প্ল্যাটফর্ম আর ABI-র ট্যাগ থাকে।

তাই প্রশ্নের উত্তর: standard-এর বাইরে থাকায় কেউ একক টুল চাপিয়ে দিতে পারেনি, আর ABI-র বৈচিত্র্যের কারণে “একটি binary সবার জন্য” কখনো সম্ভব ছিল না। ফলে আজকের সমাধান একটি নয়, দুটি প্রধান টুল, আর CMake-এর find_package তাদের সাধারণ মিলনবিন্দু।

মূল উপলব্ধি: package manager-এর কঠিন অংশ ফাইল নামানো নয়, ABI-র ম্যাট্রিক্স সামলানো। যে ভাষায় একটি compiler আর একটি build system, সেখানে এটা সহজ; C++-এ বৈচিত্র্যই শক্তি, আর সেই বৈচিত্র্যের দাম এই জটিলতা।

প্র ০২vcpkg আর Conan প্রায় একই সময়ে জন্মেছে, কিন্তু দুই ভিন্ন দর্শনে। কোথা থেকে এল তারা, আর এই পার্থক্য আজ আপনার পছন্দে কী বদলায়?

vcpkg Microsoft-এর Visual C++ দলের তৈরি, প্রকাশ্যে আসে ২০১৬ সালে। শুরুর লক্ষ্য ছিল খুব নির্দিষ্ট: Windows-এ open source C++ লাইব্রেরি ব্যবহার করা Linux-এর তুলনায় অনেক কঠিন ছিল — Linux-এ apt ছিল, Windows-এ কিছুই না। প্রথমে vcpkg শুধু Windows সমর্থন করত; পরে Linux আর macOS যোগ হয়। ২০২০-এর দিকে manifest mode (vcpkg.json) আসে, আর তার পরে baseline-ভিত্তিক versioning — যা vcpkg-কে একটি “মেশিনের টুল” থেকে “প্রজেক্টের টুল”-এ রূপান্তর করে।

Conan-এর শিকড় স্পেনের biicode নামের একটি স্টার্টআপে, যারা আগে একটি C/C++ নির্ভরতা-ব্যবস্থা বানানোর চেষ্টা করেছিল। Diego Rodriguez-Losada আর Luis Martínez de Bartolomé সেই অভিজ্ঞতা থেকে ২০১৫-১৬ সালের দিকে Conan শুরু করেন, আর অল্প পরেই JFrog (Artifactory-র নির্মাতা প্রতিষ্ঠান) দলটিকে নিজেদের সাথে নেয়। বহু বছর Conan ১.x চলার পর ২০২৩ সালে Conan 2.0 আসে — অনেক বড় পরিবর্তনসহ, যা পুরোনো রেসিপি আর কমান্ড ভেঙে দেয়। এজন্যই আজ ইন্টারনেটে Conan-এর দুই যুগের লেখা মিশে আছে।

দর্শনের পার্থক্য তাদের জন্ম থেকেই বোঝা যায়। vcpkg একটি বাছাই করা registry: সব port একসাথে, একই Git repository-তে, একসাথে পরীক্ষিত; baseline মানে “এই মুহূর্তের সব কিছু, যা একসাথে কাজ করে।” এটা অনেকটা একটি Linux distribution-এর মতো। Conan বিকেন্দ্রীভূত: যেকোনো সংখ্যক remote, প্রতিটি package-এর নিজস্ব version আর revision, আর binary-কে প্রথম শ্রেণির নাগরিক হিসেবে দেখা।

আপনার জন্য এর মানে: আপনার প্রজেক্ট CMake-ভিত্তিক, লাইব্রেরিগুলো public, আর আপনি Windows আর Linux দুটোতেই সহজ পথ চান — vcpkg সাধারণত কম ঘর্ষণের। আপনার প্রতিষ্ঠানে অনেক অভ্যন্তরীণ লাইব্রেরি, অনেক প্ল্যাটফর্ম (embedded, cross-compile), আর প্রতিটি সেটিংয়ের binary কেন্দ্রীয়ভাবে রাখতে চান — Conan-এর মডেল স্বাভাবিকভাবে মেলে। দুটোই এখন পরিণত, দুটোই CMake-এর সাথে ভালো চলে, আর অংশ ৭-এ দেখেছেন আপনার CMakeLists.txt দুই ক্ষেত্রে প্রায় হুবহু এক — তাই পরে বদলানোও অসম্ভব নয়।

মূল উপলব্ধি: একটি টুলের নকশা বুঝতে তার জন্মের সমস্যাটা দেখুন। vcpkg জন্মেছে “Windows-এ লাইব্রেরি নেই” থেকে, Conan জন্মেছে “বড় দলে binary সামলানো” থেকে — আর আজও দুটোর শক্তি ঠিক সেখানেই।

প্র ০৩২০২০ সালে Prague-এ C++ কমিটিতে “ABI ভাঙা হবে কি না” নিয়ে বিখ্যাত বিতর্ক হয়েছিল। বিতর্কটা কী ছিল, আর লাইব্রেরি ও package manager-এর ওপর এর প্রভাব কী?

প্রথমে একটি পুরোনো ঘটনা, যা অংশ ১-এর runnable কোডে দেখেছেন। C++11 standard std::string-এর জন্য এমন নিয়ম দিল যা GCC-র পুরোনো “copy-on-write” বাস্তবায়নের সাথে মেলে না। GCC-কে তাই std::string (আর std::list)-এর গঠন বদলাতে হলো — অর্থাৎ ABI ভাঙতে হলো। GCC ৫ (২০১৫) এটা করল সাবধানে: নতুন গঠনের নাম রাখল std::__cxx11::basic_string, আর _GLIBCXX_USE_CXX11_ABI macro দিয়ে পুরোনোটাও রেখে দিল। তবুও কয়েক বছর Linux জগতে “undefined reference to … __cxx11 …” error ছিল নিত্যদিনের ঘটনা। শিক্ষা: ABI ভাঙা খুব ব্যয়বহুল।

এরপর ২০২০-এর ফেব্রুয়ারিতে Prague বৈঠক। Google-এর Titus Winters “ABI: Now or Never” (P1863) নামে একটি দলিলে যুক্তি দিলেন: ABI স্থির রাখার প্রতিশ্রুতির কারণে standard library-র অনেক উন্নতি আটকে আছে। উদাহরণ: std::unordered_map-এর গঠন আধুনিক hash table-এর তুলনায় ধীর, std::regex কুখ্যাতভাবে ধীর কিন্তু বদলানো যায় না, std::unique_ptr কিছু প্ল্যাটফর্মে register-এ পাস না হয়ে মেমরি দিয়ে যায়। তাঁর প্রস্তাব: হয় একটি পরিকল্পিত ABI ভাঙা, নয়তো স্পষ্ট স্বীকার যে কখনো ভাঙা হবে না — কিন্তু অনির্দিষ্ট অবস্থা নয়।

কমিটির ভোটে কোনো স্পষ্ট পরিকল্পিত ABI ভাঙার সিদ্ধান্ত আসেনি; বাস্তবে স্থিতাবস্থাই থাকল — standard library বাস্তবায়নকারীরা (বিশেষত যাদের অপারেটিং সিস্টেম লাইব্রেরি binary আকারে বিতরণ করে) ABI ভাঙতে অনিচ্ছুক। এর পরে Google কমিটিতে নিজের অংশগ্রহণ কমিয়েছে বলে ব্যাপকভাবে আলোচিত, আর ২০২২ সালে Google-এর প্রকৌশলীরা পরীক্ষামূলক Carbon ভাষা ঘোষণা করেন — যার অনুপ্রেরণার একটি অংশ ছিল এই অচলাবস্থা। অন্যদিকে MSVC (Visual Studio 2015 থেকে 2022) নিজের binary সামঞ্জস্য বজায় রেখেছে।

লাইব্রেরি আর package manager-এর জন্য প্রভাব দুই দিকে। এক, স্থির ABI মানে system package manager আর আগে-বানানো binary-র মূল্য টিকে থাকে — Conan-এর binary মডেল এর ওপর দাঁড়িয়ে। দুই, যারা দ্রুততম container চায় তারা standard library-র বাইরে যায়: Abseil, Boost.Unordered, folly-র মতো লাইব্রেরি — যেগুলো আনতে আবার package manager লাগে। অর্থাৎ ABI স্থিরতার সিদ্ধান্ত পরোক্ষভাবে package manager-এর চাহিদা বাড়িয়েছে।

মূল উপলব্ধি: ABI হলো একটি প্রতিশ্রুতি, যা পুরোনো binary-কে বাঁচায় কিন্তু নতুন উন্নতিকে আটকায়। কোনটা বেশি দামি — সেই প্রশ্নের উত্তর ভিন্ন জনের কাছে ভিন্ন, আর এই টানাপোড়েনই C++ ইকোসিস্টেমের অনেক টুলের জন্ম দিয়েছে।

প্র ০৪npm-এর package-lock.json, Python-এর lock ফাইল, Maven-এর হুবহু version, vcpkg-এর baseline, Conan-এর conan.lock — সব ইকোসিস্টেম শেষে “lock” ধারণায় পৌঁছেছে কেন? আর লাইব্রেরি ও অ্যাপ্লিকেশনের নিয়ম আলাদা কেন?

সমস্যাটা সর্বজনীন: manifest-এ আপনি লেখেন “কী চাই” (fmt >= 10), কিন্তু build-এর জন্য লাগে “ঠিক কী পেলাম” (fmt 10.2.1, এই hash). এই দুইয়ের মাঝখানে সময় বসে আছে — আজ যে version সবচেয়ে নতুন, ছয় মাস পরে তা নয়। Lock ফাইল সেই মুহূর্তের সিদ্ধান্তকে স্থির করে রাখে, যাতে আজকের build আর ছয় মাস পরের build একই হয় — reproducible build.

প্রতিটি ইকোসিস্টেম এই শিক্ষা নিজের মতো পেয়েছে। JavaScript-এ Yarn (২০১৬) lockfile জনপ্রিয় করে, আর npm ৫ (২০১৭) থেকে package-lock.json ডিফল্ট হয় — হাজারো ছোট নির্ভরতার জগতে এটা ছাড়া চলা অসম্ভব হয়ে উঠেছিল। Python-এ বহুদিন pip freeze > requirements.txt দিয়ে কাজ চলেছে, পরে pip-tools, Poetry আর অন্য টুল আসল lock ফাইল দিয়েছে (দেখুন Python-এর packaging পর্ব)। Java-র Maven একটু ভিন্ন পথে: প্রচলিত অভ্যাস হলো range নয়, হুবহু version লেখা, আর dependencyManagement দিয়ে পুরো প্রজেক্টে একটি version বাঁধা (Java-র Maven ও Gradle পর্ব); Gradle-এ আলাদা dependency locking-ও আছে। Rust-এ Cargo.lock শুরু থেকেই।

vcpkg-এর baseline এই পরিবারের একটি আকর্ষণীয় ব্যতিক্রম। এটি প্রতিটি package আলাদা করে lock করে না — পুরো registry-র একটি commit বেছে নেয়, যেন পুরো দুনিয়ার একটি ছবি। সাথে “সর্বনিম্ন version” নিয়ম (অংশ ৭-এর runnable কোড), যার ফলে নতুন রিলিজ এলেও ফল বদলায় না। তাই vcpkg-এ আলাদা lock ফাইল ছাড়াই reproducibility আসে। Conan বেছে নিয়েছে প্রচলিত পথ: conan.lock, যেখানে প্রতিটি package-এর version আর রেসিপির revision লেখা থাকে।

এবার লাইব্রেরি বনাম অ্যাপ্লিকেশন। একটি অ্যাপ্লিকেশন হলো graph-এর শীর্ষ — তার lock ফাইল Git-এ রাখুন, কারণ তাকে নির্দিষ্ট, পরীক্ষিত রূপে চলতে হবে। একটি লাইব্রেরি অন্যের graph-এর মাঝখানে বসে। সে যদি খুব সরু version চায় (“ঠিক fmt 10.2.1”), তবে অংশ ২-এর diamond সংঘাত তৈরি করে। তাই লাইব্রেরি manifest-এ প্রশস্ত শর্ত দেয় (“fmt ১০ বা নতুন”), আর চূড়ান্ত বাঁধার কাজ ছেড়ে দেয় অ্যাপ্লিকেশনের lock-এর ওপর। npm, Cargo, Python — সব জায়গায় এই একই পরামর্শ।

মূল উপলব্ধি: manifest হলো ইচ্ছা, lock হলো স্মৃতি। ভিন্ন ভাষা, ভিন্ন টুল, কিন্তু সবাই একই সত্যে পৌঁছেছে — আর C++-এ বাড়তি একটি মাত্রা আছে: version-এর সাথে ABI-ও বাঁধতে হয়, যা triplet বা profile-এর কাজ।

প্র ০৫২০১৬ সালে মাত্র ১১ লাইনের একটি npm package (left-pad) সরিয়ে নেওয়ায় হাজারো প্রজেক্টের build ভেঙেছিল। C++-এর ইকোসিস্টেম এই ধরনের ঘটনা থেকে কী শিখতে পারে?

মার্চ ২০১৬-তে একজন ডেভেলপার একটি নামের বিরোধে বিরক্ত হয়ে npm থেকে নিজের সব package সরিয়ে নেন। তার একটি ছিল left-pad — একটি string-এর বাঁ দিকে ফাঁকা জায়গা যোগ করার প্রায় ১১ লাইনের ফাংশন। দেখা গেল, Babel-সহ বহু জনপ্রিয় টুল সরাসরি বা পরোক্ষভাবে এর ওপর নির্ভরশীল; কয়েক ঘণ্টার জন্য সারা দুনিয়ায় অসংখ্য build ভেঙে গেল। তিনটি শিক্ষা এখান থেকে: খুব ছোট কাজের জন্যও নির্ভরতা নেওয়ার প্রবণতা, transitive নির্ভরতার অদৃশ্যতা, আর একটি কেন্দ্রীয় registry-র ওপর পুরোপুরি ভরসা।

পরের বছরগুলোতে সমস্যাটা দুর্ঘটনা থেকে আক্রমণে পরিণত হয়েছে। ২০১৮-তে npm-এর event-stream package-এ নতুন “রক্ষণাবেক্ষণকারী” ক্রিপ্টো-চুরির কোড ঢোকান। আর ২০২৪-এ C জগতেই xz utils-এর ঘটনা — একজন অবদানকারী কয়েক বছর ধরে আস্থা অর্জন করে release ফাইলে লুকানো backdoor রাখেন, যা নির্দিষ্ট পরিস্থিতিতে SSH সার্ভারকে প্রভাবিত করতে পারত। Microsoft-এর একজন প্রকৌশলী, Andres Freund, SSH লগইনে আধা সেকেন্ডের মতো অস্বাভাবিক দেরি দেখে খুঁজতে খুঁজতে এটা ধরে ফেলেন, বড় Linux distribution-গুলোর স্থিতিশীল রিলিজে পৌঁছানোর আগেই।

C++-এর ইকোসিস্টেম কিছু ক্ষেত্রে স্বাভাবিকভাবেই সুরক্ষিত, কিছু ক্ষেত্রে নয়। সুবিধা: “এক ফাংশনের package” সংস্কৃতি এখানে কম, কারণ নির্ভরতা যোগ করা কষ্টের; vcpkg-এর প্রতিটি port রিভিউ হয়ে registry-তে ঢোকে, আর portfile-এ source-এর SHA512 hash লেখা থাকে, তাই কেউ GitHub-এ release ফাইল বদলে দিলে build থেমে যায়। ঝুঁকি: আমাদের নির্ভরতাগুলো প্রায়ই বড়, পুরোনো আর কম রক্ষণাবেক্ষণকারীর হাতে (xz-এর মতো), আর native কোডে একটি backdoor-এর ক্ষমতা সীমাহীন।

বাস্তব প্রতিরক্ষা অংশ ৯-এর চেকলিস্টেই আছে: baseline আর lockfile দিয়ে version বাঁধা, প্রতিটি আপডেট রিভিউ, hash যাচাই চালু রাখা, প্রতিষ্ঠানে নিজস্ব mirror বা remote (vcpkg-এর asset caching দিয়ে source ফাইলেরও কপি রাখা যায়), আর SBOM দিয়ে জানা কোন পণ্যে কোন লাইব্রেরির কোন version — যাতে নতুন নিরাপত্তা-ত্রুটির খবর এলে এক ঘণ্টায় বলা যায় “আমরা আক্রান্ত কি না।” left-pad-এর ক্ষেত্রে mirror থাকলে build ভাঙত না; xz-এর ক্ষেত্রে বাঁধা version থাকলে নতুন backdoor-যুক্ত version নিজে নিজে ঢুকত না।

মূল উপলব্ধি: প্রতিটি নির্ভরতা একটি আস্থার সিদ্ধান্ত — আপনি অন্য কারও কোডকে নিজের ব্যবহারকারীর মেশিনে চালানোর অনুমতি দিচ্ছেন। Version বাঁধা, hash যাচাই আর কম নির্ভরতা নিরাপত্তার সবচেয়ে সস্তা বীমা।

অনুশীলন

বেশিরভাগ কাজে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর উত্তর মিলিয়ে নিন।

  1. Manifest পড়ুন: একজন সহকর্মী এই vcpkg.json দিয়েছেন। configure করলে কী হবে, আর কী কী ঠিক করবেন?
    {
      "name": "shop",
      "dependencies": [
        { "name": "fmt", "version>=": "10.1.0" },
        "nlohmann_json",
        "googletest"
      ]
    }

    তিনটি সমস্যা। (১) version>= আছে কিন্তু builtin-baseline নেই — vcpkg versioning-এর জন্য baseline চায়, তাই error দেবে। vcpkg x-update-baseline --add-initial-baseline দিয়ে বসান। (২) port-এর নাম nlohmann-json (হাইফেন), আন্ডারস্কোর নয়। (৩) GoogleTest-এর port gtest. ঠিক করার পর CMake-এ নামগুলো হবে find_package(nlohmann_json CONFIG REQUIRED) আর find_package(GTest CONFIG REQUIRED) — অর্থাৎ port-নাম আর CMake-নাম আলাদা, দুটোই মিলিয়ে নিতে হয়।

  2. Linker-এর বার্তা পড়ুন: Visual Studio-তে Debug build করতে গিয়ে এই error এল। কারণ কী, আর দুটি সমাধান বলুন।
    error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MDd_DynamicDebug' in main.obj

    লাইব্রেরিটি Release আর static CRT (/MT) দিয়ে বানানো, আর আপনার main.obj Debug আর dynamic debug CRT (/MDd) দিয়ে। দুটি অমিল একসাথে: build type আর runtime. সমাধান ১ (সবচেয়ে ভালো): লাইব্রেরি package manager দিয়ে আনুন, যাতে আপনার সেটিংয়ে Debug আর Release দুই কপিই তৈরি হয় — যেমন vcpkg-এর x64-windows বা x64-windows-static-md. সমাধান ২: সত্যিই static CRT চাইলে x64-windows-static triplet নিন আর নিজের প্রজেক্টে CMAKE_MSVC_RUNTIME_LIBRARY-কে MultiThreaded$<$<CONFIG:Debug>:Debug> করুন, যাতে দুই দিক মেলে। যে সমাধান নয়: linker-এর সতর্কতা চেপে রাখা — তাতে চলার সময় heap-এর গোলমাল হতে পারে।

  3. পথ বাছুন: প্রতিটি ক্ষেত্রে কোন পথ (vendoring, apt, FetchContent, vcpkg, Conan) বাছবেন, এক লাইনে কারণসহ। (ক) একটি ২০০ লাইনের header-only CSV parser, প্রজেক্ট অফলাইন ল্যাবে build হয়। (খ) Windows আর Linux দুটোতে চলা একটি ডেস্কটপ অ্যাপ, যাতে Boost, fmt আর SQLite লাগে। (গ) একটি ব্যাংকের ৩০টি অভ্যন্তরীণ লাইব্রেরি, তিনটি প্ল্যাটফর্ম, আর কেন্দ্রীয় binary সার্ভার। (ঘ) শুধু টেস্টের জন্য GoogleTest, ছোট একটি open source লাইব্রেরিতে।

    (ক) Vendoring — ছোট, header-only, আর ইন্টারনেট নেই; লাইসেন্স ফাইলসহ third_party/-এ রাখুন আর কোন version তা লিখে রাখুন। (খ) vcpkg — দুই প্ল্যাটফর্ম, বড় লাইব্রেরি, CMake; baseline আর binary cache দিয়ে দলের সবার একই ফল। (গ) Conan — অভ্যন্তরীণ লাইব্রেরি, প্রতিটি সেটিংয়ের binary একটি private remote-এ, lockfile দিয়ে release বাঁধা (vcpkg-এর নিজস্ব registry-ও সম্ভব, কিন্তু binary-কেন্দ্রিক মডেলে Conan স্বাভাবিক)। (ঘ) FetchContent বা package manager — open source লাইব্রেরিতে ব্যবহারকারীর ঘাড়ে বাড়তি টুল না চাপানোই ভালো; FetchContent নির্দিষ্ট tag দিয়ে, আর ঐচ্ছিকভাবে আগে find_package চেষ্টা।

  4. Conan 1 থেকে 2: AI এই নির্দেশনা দিয়েছে। কোনগুলো Conan 1-এর, আর Conan 2-এ সঠিক রূপ কী?
    [requires]
    fmt/10.2.1
    [generators]
    cmake
    --- CMakeLists.txt ---
    include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
    conan_basic_setup()
    target_link_libraries(shop CONAN_PKG::fmt)

    [generators] cmake, conanbuildinfo.cmake, conan_basic_setup() আর CONAN_PKG::fmt — চারটিই Conan 1-এর, Conan 2-এ নেই। Conan 2-এ:

    [requires]
    fmt/10.2.1
    [generators]
    CMakeDeps
    CMakeToolchain
    [layout]
    cmake_layout
    --- CMakeLists.txt ---
    find_package(fmt CONFIG REQUIRED)
    target_link_libraries(shop PRIVATE fmt::fmt)

    তারপর conan install . --build=missing আর cmake --preset conan-release. লক্ষ করুন, নতুন রূপে CMakeLists.txt-এ Conan-এর কোনো উল্লেখই নেই — একই ফাইল vcpkg বা apt-এর সাথেও চলবে। এটাই Conan 2-এর বড় উন্নতি।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমvcpkg toolchain file দিয়ে প্রথমবার CMake configure করলে ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. vcpkg.json পড়ে কোন লাইব্রেরি লাগবে তা জানা
  2. Baseline আর version-শর্ত মিলিয়ে প্রতিটি লাইব্রেরির version ঠিক করা
  3. Binary cache-এ একই ABI hash-এর তৈরি package খোঁজা
  4. বাকিগুলো triplet অনুযায়ী source থেকে build করে vcpkg_installed-এ রাখা
  5. find_package config ফাইল পেয়ে imported target দেওয়া

একটি C++ লাইব্রেরি MSVC দিয়ে compile করা .lib হিসেবে পাওয়া গেছে। MinGW-এর g++ দিয়ে সেটি link করতে গেলে undefined reference আসার প্রধান কারণ কোনটি?

  • লাইব্রেরিতে ফাংশনটি আসলে নেই
  • header ফাইলের ঠিকানা ভুল
  • GCC আর MSVC-র name mangling ও ABI আলাদা
  • লাইব্রেরি C++17-এ, প্রোগ্রাম C++20-এ লেখা

vcpkg-এর vcpkg.json-এ builtin-baseline-এর কাজ কী?

  • প্রতিবার configure-এ সব লাইব্রেরিকে সর্বশেষ version-এ আপডেট করা
  • Registry-র একটি নির্দিষ্ট commit বেছে নিয়ে সব port-এর ডিফল্ট version বেঁধে দেওয়া
  • কোন compiler দিয়ে লাইব্রেরি build হবে তা ঠিক করা
  • Binary cache কোথায় থাকবে তা জানানো

Conan 2-এ conan install . --build=missing কমান্ডের --build=missing অংশের অর্থ কী?

  • সব নির্ভরতা সবসময় source থেকে নতুন করে build করা
  • যে নির্ভরতা পাওয়া যায় না, সেটি বাদ দিয়ে এগিয়ে যাওয়া
  • আপনার নিজের প্রজেক্টের যে ফাইলগুলো build হয়নি সেগুলো build করা
  • যেসব package-এর মিলে যাওয়া আগে-বানানো binary নেই, শুধু সেগুলো source থেকে build করা

vcpkg দিয়ে nlohmann/json বসানোর পর CMake-এ সঠিক লাইন কোনটি?

  • find_package(nlohmann_json CONFIG REQUIRED) আর nlohmann_json::nlohmann_json
  • find_package(nlohmann-json CONFIG REQUIRED) আর nlohmann-json::json
  • find_package(json REQUIRED) আর json::json
  • vcpkg_link(nlohmann)

একটি অ্যাপ্লিকেশনের দুটি নির্ভরতার একটি fmt ১০ চায় আর অন্যটি ঠিক fmt ৮ চায়। এই অবস্থাকে কী বলে?

  • ABI break
  • Diamond dependency সংঘাত
  • Vendoring
  • Name mangling

Conan 2-এর কোন দুটি generator একসাথে CMake প্রজেক্টকে find_package দিয়ে package খুঁজতে আর ঠিক compiler সেটিং পেতে সাহায্য করে?

  • cmake আর cmake_find_package
  • conanbuildinfo আর conan_basic_setup
  • CMakeDeps আর CMakeToolchain
  • vcpkg.cmake আর CMakePresets

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. C++ লাইব্রেরি মানে ABI. Compiler, standard library, build type, runtime আর প্ল্যাটফর্ম না মিললে compiled লাইব্রেরি link হয় না বা ভুল চলে; header-only লাইব্রেরি এই ঝামেলা কমায়।
  2. Package manager তিন প্রশ্নের উত্তর ফাইলে রাখে: কোন লাইব্রেরি, কোন version, কোন ABI — আর CMakeLists.txt-এ থাকে শুধু find_package + target_link_libraries.
  3. vcpkg: vcpkg.json + builtin-baseline + triplet; CMake-এ toolchain file বা preset; binary cache আর overlay port দলের জন্য।
  4. Conan 2: profile + conanfile → conan install . --build=missing → CMakeDeps আর CMakeToolchain → cmake --preset; lockfile আর private remote দলের জন্য।
  5. Version বাঁধুন, লাইসেন্স দেখুন, নাম মিলিয়ে নিন: port-নাম, CMake-নাম আর target-নাম আলাদা হতে পারে — AI-এর দেওয়া নাম registry আর ডকুমেন্টেশনে যাচাই করুন।

পরের পর্বে: নির্ভরতা এখন ঠিকঠাক আসে, GoogleTest-ও তৈরি। কিন্তু আপনার কোড ঠিক কাজ করছে — সেটা প্রমাণ করবেন কীভাবে, আর বাগ এলে খুঁজবেন কীভাবে? পর্ব ৫১-এ টেস্টিং ও ডিবাগিং: unit test, GoogleTest, debugger আর CI.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান