C Nix boilerplate

Date:

Tags: #nix

Looking to start out a C (or C++) project and wanna some cool and nifty commands to manage it through both make and nix? As a plus, you’ll also get nice out of tree builds.

Then look no further, here’s all the boilerplate you need.

If you just want it fast without an explanation, it’s all available on my repository.

You can just run nix flake init -t github:misterio77/nix-config#templates.c, to quickly init a flake with this template.

We’re gonna start by building a flexible (yet simple) Makefile:

# Set default prefix and bin dirs, for non-nixos
PREFIX ?= /usr/local
BIN_DIR ?= $(PREFIX)/bin

# Executable name
TARGET_EXEC ?= foo-bar
# Build directory
BUILD_DIR ?= ./build
# Source directory(ies)
SRC_DIRS ?= ./src

# Use find to get all .c and .cpp files
SRCS := $(shell find $(SRC_DIRS) -name *.cpp -or -name *.c)
# Map those to their respective .o files
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
# Dependency files generated by gcc
DEPS := $(OBJS:.o=.d)

# Directories with includes
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Flags for including those
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

# Flags for both C and C++
CPPFLAGS ?= $(INC_FLAGS) -MMD -MP

# Link executable
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
	$(CXX) $(OBJS) -o $@ $(LDFLAGS)

# C source files
$(BUILD_DIR)/%.c.o: %.c
	$(MKDIR_P) $(dir $@)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# C++ source files
$(BUILD_DIR)/%.cpp.o: %.cpp
	$(MKDIR_P) $(dir $@)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

# Clean built artifacts
clean:
	$(RM) -r $(BUILD_DIR)

# Install to prefix/bin path
install: $(BUILD_DIR)/$(TARGET_EXEC)
	install -d $(BIN_DIR)
	install -t $(BIN_DIR) $<

# Include generated dependencies
-include $(DEPS)

MKDIR_P ?= mkdir -p

This is heavily based on this post, with the main change being the install directive.

Okay, now we just need a flake for building with nix:

{
  description = "Foo Bar C/C++ Project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    # For each system
    flake-utils.lib.eachDefaultSystem (system:
      let
        # Derivation and executable name
        name = "foo-bar";
        pkgs = (import nixpkgs { inherit system; });
      in
      rec {
        # nix build
        packages.${name} = pkgs.stdenv.mkDerivation rec {
          inherit name;
          src = ./.;
          # Set prefix, so stuff is installed to $out
          makeFlags = [ "PREFIX=$(out)" ];
        };
        defaultPackage = packages.${name};

        # nix run
        apps.${name} = {
          type = "app";
          program = "${packages.${name}}/bin/${name}";
        };
        defaultApp = apps.${name};

        # nix develop
        devShell = pkgs.mkShell {
          # Add clang and clang-tools for LSP support while editing
          buildInputs = with pkgs; [ gnumake clang clang-tools ];
        };
      });
}

And there you go!