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";
};
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
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}"
];
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
'';
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}"
)
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)
Make sure to include the FetchContent
module and link the libraries!
include(FetchContent)
# ...
target_link_libraries(program PRIVATE
NAME::module
)
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;
};
};
}
);
};
}
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)
Good luck Nixing 🫡