Nix + CMake FetchContent Workaround
Hadrian

Hadrian @udontur

About: Welcome to my hadrian.cc blogs!

Joined:
Dec 1, 2024

Nix + CMake FetchContent Workaround

Publish Date: Jul 9
0 0

Introduction

When you run nix build, Nix builds your code with instructions stated in the flake.nix file in a sandbox. Problem? A sandbox does not have internet access. If you use CMake with the FetchContent module, your build will fail because CMake cannot download tarballs from the URL.

Workaround

We need to find a way for CMake FetchContent to download tarballs in Nix builds' sandbox. To do that, we can download the tarballs first before building with CMake.

Nix Part

We can utilize the builtins.fetchTarball module provided by Nix, and pass it as a variable:

NAME_src = builtins.fetchTarball{
  url = "https://github.com/USER/REPO/archive/refs/tags/VERSION.tar.gz";
  sha256 = "sha256:HASH";
};
Enter fullscreen mode Exit fullscreen mode

Replace the NAME_src variable with a variable of your choice and the replace the URL with the repo's tarball link. You can get the sha256 hash from the following command:

nix-prefetch-url --unpack REPO_TARBALL_URL
Enter fullscreen mode Exit fullscreen mode

Now, we need to let cmake recognize them. So, in the configurePhase, we pass the variables as an environment variable to CMake:

configurePhase = ''
  export NAME_src=${NAME_src}
'';
# ...
cmakeFlags = [
  "-DNAME_src=${NAME_src}"
];
Enter fullscreen mode Exit fullscreen mode

With these modifications, Nix does not automatically recognize CMake, so you must write your own buildPhase and installPhase:

buildPhase = ''
  mkdir -p build
  cd build
  cmake ..
  cmake --build .
'';
installPhase = ''
  runHook preInstall
  mkdir -p $out/bin
  install -Dm755 ./um $out/bin/um
  runHook postInstall
'';
Enter fullscreen mode Exit fullscreen mode

CMake Part

Instead of pulling the source code from the internet, we define its source directory using the environment variables we passed from Nix:

FetchContent_Declare(
  NAME
  SOURCE_DIR "$ENV{NAME_src}"
)
Enter fullscreen mode Exit fullscreen mode

However, this approach would not be compatible with non-Nix builds, so we can configure CMake to use this method only when the corresponding variable is defined:

if (DEFINED ENV{NAME_src})
  FetchContent_Declare(
    NAME
    SOURCE_DIR "$ENV{NAME_src}"
  )
else ()
  FetchContent_Declare(
    NAME
    GIT_REPOSITORY https://github.com/USER/REPO
    GIT_TAG VERSION
  )
endif ()
FetchContent_MakeAvailable(NAME)
Enter fullscreen mode Exit fullscreen mode

Make sure to include the FetchContent module and link the libraries!

include(FetchContent)
# ...
target_link_libraries(program PRIVATE
  NAME::module
)
Enter fullscreen mode Exit fullscreen mode

Example

Here is an example from one of my projects:

flake.nix

{
  description = "github:udontur/umpire Nix flake";
  inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";

  outputs = { self, nixpkgs, ... }:
    let
      supportedSystems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
    in{
      packages = forAllSystems(system:
      let
        pkgs = import nixpkgs {
          inherit system;
        };
        fmt_src = builtins.fetchTarball{
          url = "https://github.com/fmtlib/fmt/archive/refs/tags/11.2.0.tar.gz";
          sha256 = "sha256:0x8j1k1cnmvv5hbhhyfm7bqw2d2rb3jpmz6bc4a195z8pzj582dh";
        };

        ftxui_src = builtins.fetchTarball{
          url = "https://github.com/ArthurSonzogni/FTXUI/archive/refs/tags/v6.1.9.tar.gz";
          sha256 = "sha256:1sbgp1b5v0z8dcfwmgincd759brc1bz64vifdfp4r1afp1672lm6";
        };

        argparse_src = builtins.fetchTarball{
          url = "https://github.com/p-ranav/argparse/archive/refs/tags/v3.2.tar.gz";
          sha256 = "sha256:04wp0q5sxn4hlqzsbq1g99q4k8wn0y1y2ldwld7g3k7yibqi90n3";
        };

      in{
        default =
          pkgs.stdenv.mkDerivation rec {
            pname = "umpire";
            version = "0.1";
            src = self;

            # Packages used by the builder
            nativeBuildInputs = with pkgs;[
              cmake
              gnumake
              gcc
              git
            ];

            configurePhase = ''
              export fmt_src=${fmt_src}
              export ftxui_src=${ftxui_src}
              export argparse_src=${argparse_src}
            '';

            cmakeFlags = [
              "-DCMAKE_BUILD_TYPE=Release"
              "-Dfmt_src=${fmt_src}"
              "-Dftxui_src=${ftxui_src}"
              "-Dargparse_src=${argparse_src}"
            ];

            buildPhase = ''
              mkdir -p build
              cd build
              cmake ..
              cmake --build .
            '';


            installPhase = ''
              runHook preInstall

              mkdir -p $out/bin
              install -Dm755 ./um $out/bin/um
              ln -s $out/bin/um $out/bin/umpire

              runHook postInstall
            '';


            meta = {
              homepage = "https://github.com/udontur/umpire";
              description = "A blazingly fast competitive programming helper";
              mainProgram = "umpire";
              license = pkgs.lib.licenses.mit;
              platforms = pkgs.lib.platforms.all;
            };
          };
        }
      );
    };
}
Enter fullscreen mode Exit fullscreen mode

CMakeLists.txt

cmake_minimum_required(VERSION 3.27)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
project(
    umpire 
    DESCRIPTION "A blazingly fast competitive programming helper"
    LANGUAGES CXX
)

include(FetchContent)

if (DEFINED ENV{fmt_src})
    message("Fetching fmt source from env var: $ENV{fmt_src}")
    FetchContent_Declare(
        fmt
        SOURCE_DIR "$ENV{fmt_src}"
    )
else ()
    message("Fetching fmt source from git")
    FetchContent_Declare(
        fmt
        GIT_REPOSITORY https://github.com/fmtlib/fmt
        GIT_TAG 11.2.0
    )
endif ()
FetchContent_MakeAvailable(fmt)

if (DEFINED ENV{ftxui_src})
    message("Fetching FTXUI source from env var: $ENV{ftxui_src}")
    FetchContent_Declare(
        ftxui
        SOURCE_DIR "$ENV{ftxui_src}"
    )
else ()
    message("Fetching FTXUI source from git")
    FetchContent_Declare(
        ftxui
        GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
        GIT_TAG v6.1.9
    )
endif ()
FetchContent_MakeAvailable(ftxui)

if (DEFINED ENV{argparse_src})
    message("Fetching argparse source from env var: $ENV{argparse_src}")
    FetchContent_Declare(
        argparse
        SOURCE_DIR "$ENV{argparse_src}"
    )
else ()
    message("Fetching argparse source from git")
    FetchContent_Declare(
        argparse
        GIT_REPOSITORY https://github.com/p-ranav/argparse
        GIT_TAG v3.2
    )
endif ()
FetchContent_MakeAvailable(argparse)

include_directories(src)
add_executable(um src/main.cpp)

target_link_libraries(um PRIVATE 
    fmt::fmt 
    argparse
    ftxui::screen
    ftxui::dom
    ftxui::component
)

target_include_directories(um PRIVATE src)
Enter fullscreen mode Exit fullscreen mode

Good luck Nixing 🫡

Comments 0 total

    Add comment