C: Difference between revisions
imported>Mic92 mention debug symbols |
No edit summary |
||
| (37 intermediate revisions by 16 users not shown) | |||
| Line 1: | Line 1: | ||
This | This is a collection of recipes for working on C/C++ projects with Nix. | ||
They do not just apply to C but also C++. | |||
== Differences between nixpkgs and the rest == | == Differences between nixpkgs and the rest == | ||
The way nixpkgs and its stdenv handles compiling and linking is very different from other | The way nixpkgs and its stdenv handles compiling and linking is very different from other Linux distributions. | ||
In more conventional Linux distributions it's usual that header files are put into well known paths i.e. <code>/usr/include</code>, where the compiler will look for them. | |||
look for them. Same is true when linking against libraries, which are put in a few places, where the build-time | Same is true when linking against libraries, which are put in a few places, where the build-time linker will find them. | ||
linker will find them. Dynamically linked libraries will have a run-time linker (also known as <code>ld.so</code>) set as an interpreter. | Dynamically linked libraries will have a run-time linker (also known as <code>ld.so</code>) set as an interpreter. | ||
This linker reads <code>/etc/ld.so.conf</code> to figure out where to find libraries. | This linker reads <code>/etc/ld.so.conf</code> to figure out where to find libraries. | ||
In nixpkgs in contrast this information is provided by environment variables | In nixpkgs in contrast this information is provided by environment variables. | ||
be set based on the build inputs that are given when building a package or | Those will be set based on the build inputs that are given when building a package or | ||
when loading a nix expression into a <code>nix-shell</code>. | when loading a nix expression into a <code>nix-shell</code>. | ||
Therefore it is not sufficient to just install libraries with <code>nix-env</code> into the profile | Therefore it is not sufficient to just install libraries with <code>nix-env</code> into the profile | ||
| Line 39: | Line 40: | ||
<syntaxHighlight lang=console> | <syntaxHighlight lang=console> | ||
$ cat > shell.nix <<EOF | $ cat > shell.nix <<EOF ;nix-shell | ||
with import <nixpkgs> {}; | with import <nixpkgs> {}; | ||
stdenv.mkDerivation { | stdenv.mkDerivation { | ||
name = "myenv"; | name = "myenv"; | ||
buildInputs = [ zlib ]; | buildInputs = [ zlib ]; | ||
} | } | ||
EOF | EOF | ||
[nix-shell:~] $ echo $NIX_CFLAGS_COMPILE | [nix-shell:~] $ echo $NIX_CFLAGS_COMPILE | ||
| Line 54: | Line 55: | ||
In <code>$NIX_CFLAGS_COMPILE</code> we see that the include search path is extended by appending new directories | In <code>$NIX_CFLAGS_COMPILE</code> we see that the include search path is extended by appending new directories | ||
using the <code>-isystem</code> flag. | using the <code>-isystem</code> flag. | ||
However, while the <code>$out/include</code> folder will be included by default, it may sometimes not be enough when the lib puts the header in a subfolder (for instance, gtk2 and gtk3 uses subdirectories like <code>$out/include/gtk-2.0</code> instead to avoid conflict between versions). To deal with this kind of libraries, one can use <code>pkg-config</code>: the idea is simply to add <code>pkg-config</code> in the <code>nativeBuildInputs</code>, and then to start the <code>buildPhase</code> with: | |||
<syntaxHighlight lang=nix> | |||
buildPhase = '' | |||
NIX_CFLAGS_COMPILE="$(pkg-config --cflags gtk+-3.0) $NIX_CFLAGS_COMPILE" | |||
# put the usual make/gcc code here | |||
''; | |||
</syntaxHighlight> | |||
For <code>$NIX_LDFLAGS</code> see that the library link path is extended | For <code>$NIX_LDFLAGS</code> see that the library link path is extended | ||
| Line 96: | Line 105: | ||
== Hardening flags == | == Hardening flags == | ||
To improve the security of applications the wrapper also injects additional hardening compile flags into the application. | To improve the security of applications the wrapper also injects additional hardening compile flags into the application. These nix flags enable different compiler flags, as seen in the [https://nixos.org/nixpkgs/manual/#sec-hardening-in-nixpkgs manual]. | ||
Under some circumstances this can make programs fail to build or function. | Under some circumstances this can make programs fail to build or function. For example, the <code>fortify</code> flag enables the <code>-O2</code> optimization level -- if you want to change this, you need to disable the <code>fortify</code> flag and re-add the compiler flags manually (<code>env.NIX_CFLAGS_COMPILE = [ "-O" "....."]</code>). | ||
To disable all hardening options one can export the environment variable <code>hardeningDisable="all"</code>. | To disable all hardening options one can export the environment variable <code>hardeningDisable="all"</code>. | ||
This also works for derivations like that: | This also works for derivations like that: | ||
| Line 108: | Line 118: | ||
</syntaxHighlight> | </syntaxHighlight> | ||
It is also possible to only disable certain parts, for example <code>-Werror=format-security</code>: | |||
<syntaxHighlight lang=nix> | <syntaxHighlight lang=nix> | ||
| Line 135: | Line 145: | ||
stdenv.mkDerivation { | stdenv.mkDerivation { | ||
name = "env"; | name = "env"; | ||
nativeBuildInputs = [ | nativeBuildInputs = [ pkg-config ]; | ||
buildInputs = [ | buildInputs = [ | ||
cryptsetup | cryptsetup | ||
| Line 149: | Line 159: | ||
[nix-shell:~] $ pkg-config --cflags libcryptsetup | [nix-shell:~] $ pkg-config --cflags libcryptsetup | ||
-I/nix/store/ypg1r7c8m0rkim7by4ikn68xc88bi53j-cryptsetup-2.0.6-dev/include | -I/nix/store/ypg1r7c8m0rkim7by4ikn68xc88bi53j-cryptsetup-2.0.6-dev/include | ||
</syntaxHighlight> | |||
When using <code>autoconf</code>, pkg-config is a required build input for providing the <code>AC_CHECK_HEADERS</code> m4 macro. | |||
=== pkg-config package names === | |||
To list all pkg-config package names of a Nix package: | |||
<syntaxHighlight lang=console> | |||
$ nix-shell -p pkgconfig libglvnd | |||
$ pkg-config --list-all | |||
egl egl - EGL library and headers | |||
libglvnd libglvnd - Vendor-neutral OpenGL dispatch library vendor interface | |||
glx glx - GLX library and headers | |||
glesv1_cm glesv1_cm - OpenGL ES-CM v1 library and headers | |||
opengl opengl - OpenGL (without GLX) headers and interface | |||
glesv2 gles2 - OpenGL ES v2/v3 library and headers | |||
gl gl - Legacy OpenGL and GLX library and headers | |||
</syntaxHighlight> | </syntaxHighlight> | ||
| Line 166: | Line 194: | ||
<syntaxHighlight lang=console> | <syntaxHighlight lang=console> | ||
$ nix-shell | $ nix-shell | ||
[nix-shell:~] $ echo $CMAKE_PREFIX_PATH | [nix-shell:~] $ echo $CMAKE_PREFIX_PATH | ||
/nix/store/lw4xr0x2p6xyfgbk961lxh8vnnx7vn2r-cmake-3.12.1:/nix/store/j4x44bjjgwy7hm7lazj8xnr9mnlfiksh-patchelf-0.9:/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0:/nix/store/lwdkm354f3zzsvkf7pqmnc8w6r164b42-binutils-wrapper-2.30:/nix/store/biz7v9g4g6yrnp2h8wfn01d6pk3bj2m1-zeromq-4.3.0:/nix/store/lw4xr0x2p6xyfgbk961lxh8vnnx7vn2r-cmake-3.12.1:/nix/store/j4x44bjjgwy7hm7lazj8xnr9mnlfiksh-patchelf-0.9:/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0:/nix/store/lwdkm354f3zzsvkf7pqmnc8w6r164b42-binutils-wrapper-2.30:/nix/store/biz7v9g4g6yrnp2h8wfn01d6pk3bj2m1-zeromq-4.3.0 | /nix/store/lw4xr0x2p6xyfgbk961lxh8vnnx7vn2r-cmake-3.12.1:/nix/store/j4x44bjjgwy7hm7lazj8xnr9mnlfiksh-patchelf-0.9:/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0:/nix/store/lwdkm354f3zzsvkf7pqmnc8w6r164b42-binutils-wrapper-2.30:/nix/store/biz7v9g4g6yrnp2h8wfn01d6pk3bj2m1-zeromq-4.3.0:/nix/store/lw4xr0x2p6xyfgbk961lxh8vnnx7vn2r-cmake-3.12.1:/nix/store/j4x44bjjgwy7hm7lazj8xnr9mnlfiksh-patchelf-0.9:/nix/store/isg8rxaxkijl9x3hr2gzsf8pqfnqxg3k-gcc-wrapper-7.4.0:/nix/store/lwdkm354f3zzsvkf7pqmnc8w6r164b42-binutils-wrapper-2.30:/nix/store/biz7v9g4g6yrnp2h8wfn01d6pk3bj2m1-zeromq-4.3.0 | ||
[nix-shell:~] $ cat >CMakeLists.txt <<EOF | [nix-shell:~] $ cat >CMakeLists.txt <<EOF | ||
| Line 176: | Line 204: | ||
[nix-shell:~] $ echo 'int main {}' > hello.c | [nix-shell:~] $ echo 'int main {}' > hello.c | ||
[nix-shell:~] $ cmake . | [nix-shell:~] $ cmake . | ||
</syntaxHighlight> | |||
== gcc multilib == | |||
<code>pkgs.gcc_multi</code> exports a <code>gcc</code> in a multilib variant, which can produce 32-bit and 64-bit x86 code at the same time. However, <code>gcc_multi</code> falls back to the gcc version coming from <code>pkgs.gcc</code>. To use a specific version of gcc, you might use something like that: | |||
<syntaxHighlight lang=nix> | |||
{ | |||
gcc11_multi = pkgs.wrapCCMulti pkgs.gcc11; | |||
// or | |||
gcc13_multi = pkgs.wrapCCMulti pkgs.gcc13; | |||
} | |||
</syntaxHighlight> | </syntaxHighlight> | ||
| Line 201: | Line 241: | ||
== Debug symbols == | == Debug symbols == | ||
See also: [[Debug Symbols]] | |||
By default debug symbols are stripped of in the fixup phase of a package build. | By default debug symbols are stripped of in the fixup phase of a package build. | ||
| Line 224: | Line 266: | ||
This is described in the [https://nixos.org/nixpkgs/manual/#ssec-fixup-phase manual]. | This is described in the [https://nixos.org/nixpkgs/manual/#ssec-fixup-phase manual]. | ||
Also see [[Debug Symbols]] for further information about debug symbols. | Also see [[Debug Symbols]] for further information about debug symbols. | ||
== Editor/IDE integration == | |||
Tooling that provides autocompletion or refactoring support also needs to be aware of the environments variables | |||
to find C/C++ header files. Nixpkgs adds wrapper to all language server [https://clangd.llvm.org/ clangd] (recommend), [https://github.com/MaskRay/ccls ccls] and [https://github.com/cquery-project/cquery cquery] to extend the include path of these tools. [https://github.com/MaskRay/ccls/wiki CCLS] also provides extensive documentation on how to setup a project/editors to make use of it. | |||
But this is not enough for all projects. If you use clangd and your project generates a <code>compile_commands.json</code> which refers to a compiler which is in <code>/nix/store</code> you will need to set up your editor to start clangd with the following flags <code>clangd --query-driver=/nix/store/*/bin/g++ --query-driver=/nix/store/*/bin/gcc</code>. Clangd needs to query the compiler you are actually using to be able to find where the standard library headers are. This involves executing other binaries so clangd considers this unsecure for non-standard compiler paths. The flags whitelists all gcc and g++ compilers in the nix store. | |||
== Use a different compiler version == | |||
Adding a different c compiler to <code>buildInputs</code> in a nix expression will not change the default compiler | |||
available in <code>$PATH</code>. Instead, nixpkgs provides a several alternative <code>stdenv</code> which you can search with <code>nix search nixpkgs stdenv</code> so for example: | |||
<syntaxHighlight lang=nix> | |||
gcc8Stdenv.mkDerivation { | |||
name = "env"; | |||
} | |||
</syntaxHighlight> | |||
<syntaxHighlight lang=console> | |||
$ nix-shell --command 'gcc --version' | |||
gcc (GCC) 8.3.0 | |||
</syntaxHighlight> | |||
* Available gcc based stdenv variants: gcc{49,6-12}Stdenv, gccMultiStdenv (32bit/64bit mixed support) | |||
* Available clang based stdenv variants: llvmPackages_[5-13].{stdenv,libcxxStdenv}, clangMultiStdenv (32bit/64bit mixed support) | |||
Those stdenv instances can be also constructed using the <code>overrideCC</code> function: | |||
Here we are creating a shell environment that will always have the latest available gcc: | |||
<syntaxHighlight lang=nix> | |||
(overrideCC stdenv gcc_latest).mkDerivation { | |||
name = "env"; | |||
} | |||
</syntaxHighlight> | |||
Note that this will only affect compiler and not the used linker. To overwrite the linker and maybe also the used libc | |||
Check out the <code>wrapCCWith</code> example in the next section. | |||
See also: [[Using Clang instead of GCC]] | |||
== Get a compiler without default libc == | |||
By default cc wrapper will include the libc headers (i.e. glibc). This can break for example projects that would bring their own libc (i.e. musl). However it is possible to get a cc wrapper that would include all build inputs without adding glibc: | |||
<syntaxHighlight lang=nix> | |||
let | |||
gcc_nolibc = wrapCCWith { | |||
cc = gcc.cc; | |||
bintools = wrapBintoolsWith { | |||
bintools = binutils-unwrapped; | |||
libc = null; | |||
}; | |||
}; | |||
in (overrideCC stdenv gcc_nolibc).mkDerivation { | |||
name = "env"; | |||
} | |||
</syntaxHighlight> | |||
== Override binutils == | |||
This example shows how to apply changes to the binutils package and than use the override binutils | |||
package to compose a new stdenv. | |||
<syntaxHighlight lang=nix> | |||
with import <nixpkgs> {}; | |||
let | |||
binutils-unwrapped' = binutils-unwrapped.overrideAttrs (old: { | |||
name = "binutils-2.37"; | |||
src = pkgs.fetchurl { | |||
url = "https://ftp.gnu.org/gnu/binutils/binutils-2.37.tar.xz"; | |||
sha256 = "sha256-gg2XJPAgo+acszeJOgtjwtsWHa3LDgb8Edwp6x6Eoyw="; | |||
}; | |||
patches = []; | |||
}); | |||
cc = wrapCCWith rec { | |||
cc = gcc-unwrapped; | |||
bintools = wrapBintoolsWith { | |||
bintools = binutils-unwrapped'; | |||
libc = glibc; | |||
}; | |||
}; | |||
in | |||
(overrideCC stdenv cc).mkDerivation { | |||
name = "env"; | |||
} | |||
</syntaxHighlight> | |||
== Faster GCC compiler == | |||
The default gcc compiler in nixpkgs disables profile-guided optimization in order to achieve deterministic builds. There is a faster version available in nixpkgs via the <code>fastStdenv</code> | |||
attribute (7-12% faster). | |||
<syntaxHighlight lang=nix> | |||
fastStdenv.mkDerivation { | |||
name = "env"; | |||
} | |||
</syntaxHighlight> | |||
== Use a clang compiled from source == | |||
Unwrapped compilers usually do not have any access to libraries/headers in nix. This is an issue if you work on the clang/llvm code base. Assuming you have built llvm/clang like this | |||
<syntaxHighlight lang=console> | |||
$ git clone https://github.com/llvm/llvm-project | |||
$ cd llvm-project | |||
$ nix-shell -p cmake --command 'mkdir build && cd build && cmake -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_INSTALL_PREFIX=../install -DCMAKE_BUILD_TYPE=Debug ../llvm && make -j$(nproc)' | |||
</syntaxHighlight> | |||
You can create a wrapper around your local build binaries like this: | |||
<syntaxHighlight lang=nix> | |||
# This file assumes that your llvm binaries are stored in ./build/bin | |||
# impure-clang.nix | |||
{ stdenv, wrapCC, runtimeShell }: | |||
wrapCC (stdenv.mkDerivation { | |||
name = "impure-clang"; | |||
dontUnpack = true; | |||
installPhase = '' | |||
mkdir -p $out/bin | |||
for bin in ${toString (builtins.attrNames (builtins.readDir ./build/bin))}; do | |||
cat > $out/bin/$bin <<EOF | |||
#!${runtimeShell} | |||
exec "${toString ./.}/build/bin/$bin" "\$@" | |||
EOF | |||
chmod +x $out/bin/$bin | |||
done | |||
''; | |||
passthru.isClang = true; | |||
}) | |||
</syntaxHighlight> | |||
Then you can create a <code>shell.nix</code> like this: | |||
<syntaxHighlight lang=nix> | |||
with import <nixpkgs> {}; | |||
pkgs.mkShell { | |||
nativeBuildInputs = [ | |||
cmake | |||
(callPackage ./impure-clang.nix {}) | |||
]; | |||
} | |||
</syntaxHighlight> | |||
And use your self-compiled clang in a nix-shell: | |||
<syntaxHighlight lang=console> | |||
$ nix-shell --command 'clang --version' | |||
</syntaxHighlight> | |||
== Further information == | |||
[https://www.youtube.com/watch?v=zEsT4fw1pL0 Nix Friday about C/CPP infrastructure in Nix] | |||
[https://nixcademy.com/posts/cpp-with-nix-in-2023-part-1-shell/ C++ with Nix in 2023, Part 1: Developer Shells] | |||
[https://nixcademy.com/posts/cpp-with-nix-in-2023-part-2-package/ C++ with Nix in 2023, Part 2: Package Generation and Cross-Compilation] | |||
[[Category:Languages]] | |||
[[Category:Cookbook]] | |||