created:: 2024-01-17T10:28:08 (UTC -05:00) tags:: nixos source:: https://johns.codes/blog/efficient-nix-derivations-with-file-sets author:: John Murray - Efficient Nix Derivations with File Sets > ## Excerpt > Using nix's new file set API to make efficient derivations --- Table of Contents - [What are file sets](https://johns.codes/blog/efficient-nix-derivations-with-file-sets#what-are-file-sets) - [A Real Example](https://johns.codes/blog/efficient-nix-derivations-with-file-sets#a-real-example) - [Wrap up](https://johns.codes/blog/efficient-nix-derivations-with-file-sets#wrap-up) If you are using Nix to build your own packages you will eventually come across something like ```
stdenv.mkDerivation {
name = "my awesome pkg";
src = ./.;
buildPhase = ''
# I have no idea if this actually works (the gcc call bit)
# but the vibe is what I care about
gcc main.c -o my_program
mkdir -p $out
cp my_program $out
'';
}
let
fs = pkgs.lib.fileset;
baseSrc = fs.unions [ ./Makefile ./src ];
filterMarkdownFiles = fs.fileFilter (file: hasSuffix ".md" file.name) ./.;
removedMarkedDown = fs.difference baseSrc filterMarkdownFiles;
in
stdenv.mkDerivation {
name = "my awesome pkg";
src = fs.toSource {
root = ./.;
fileset = removedMarkedDown;
};
buildPhase = ''
# call make or w/e you want
'';
}
❯ exa --tree --level 2
.
├── Cargo.lock
├── Cargo.toml
├── host.c
├── main.roc
├── rust-toolchain.toml
└── src
├── glue.rs
├── lib.rs
└── main.rs
❯ exa --tree <compiled folder>
<compiled folder>
├── linux-x64.o
└── main.roc
let
compiledC = mkDerivation {
src = ./.;
# compile the c ...
};
rustBuiltLib = buildRustPackage {
src = ./.;
# build the rust
};
in
llvmPkgs.stdenv.mkDerivation rec {
name = "${pname}-${version}";
srcs = [
rustBuiltLib
compiledC
./. # for the roc code
];
sourceRoot = ".";
buildPhase = ''
# link the rust and c files
# copy roc and linked object out
'';
}
{lib}:
# Note i generally don't like doing `with` at the top of a file
# but since this will be only fileSet stuff it should be fine
with lib.fileset;
let
# helper func to take in a list of allowed
# returns a function of `file => bool` to be used in a fileFilter.
# true if file has suffix, false if not
fileHasAnySuffix = fileSuffixes: file: (lib.lists.any (s: lib.hasSuffix s file.name) fileSuffixes);
# given a basePath src path, return a fileset of files in that path that are rust files, toml files, or cargo toml/lock
rustFilter = basePath: (
let
mainFilter = fileFilter
(fileHasAnySuffix [ ".rs" ".toml" ])
basePath;
in
unions [ mainFilter (basePath + "/Cargo.toml") (basePath + "/Cargo.lock") ]
);
# given a basePath src path return a fileset with files ending with `.c`
cFilter = basePath: fileFilter (fileHasAnySuffix [ ".c" ]) basePath;
# given a basePath src path return a fileset with files ending with `.roc`
rocFilter = basePath: fileFilter (fileHasAnySuffix [ ".roc" ]) basePath;
in
{
inherit rustFilter cFilter rocFilter;
}
let
fs = lib.fileset;
languageFilters = import ./languageFilters.nix {inherit lib;};
baseDir = ./.;
compiledC = mkDerivation {
src = fs.toSource {
root = baseDir;
fileset = languageFilters.cFilter baseDir;
};
# compile the c ...
};
rustBuiltLib = buildRustPackage {
src = fs.toSource {
root = baseDir;
fileset = languageFilters.rustFilter baseDir;
};
# build the rust
};
rocCode = fs.toSource {
root = baseDir;
fileset = languageFilters.rocFilter baseDir;
};
in
llvmPkgs.stdenv.mkDerivation rec {
name = "${pname}-${version}";
srcs = [
rustBuiltLib
compiledC
rocCode
];
sourceRoot = ".";
buildPhase = ''
# NOTE: this link could be pulled into its own derivation for even better seperation
# I only just realized this while making this post...
$LD -r -L ${rustBuildName}/lib ${cBuildName}/${cHostDest} -lhost -o ${host_dest}
mkdir -p $out
cp ${host_dest} $out/${host_dest}
cp -r ${rocCode}/. $out
'';
}