Welcome, fellow C enthusiasts, to a whimsical journey through the world of library dependencies in C programming! This is the second post in the series "Mastering C with Effective C" series! Not at all suprising to me, I've yet to write anything at all from the book, and I'm still poking around with build systems. I've been learning to use nix as a build system, and some things are becoming clear when learning C, which has no built in package manager...
Today, we will dive into the exciting realm of building C programs with external libraries, and we’ll showcase how to do it using Nix, a modern build system that promises reproducibility and simplicity. But don't worry, we’ll also touch upon the classic Makefile approach for comparison. Let’s get started!
The code from this post can be found here
The Challenge of Library Dependencies in C
Before we embark on our adventure, let's acknowledge the challenge: managing external libraries in C. While modern languages often come with robust package managers, C developers often find themselves navigating the rugged terrain of manual downloads and builds. But fear not! With the power of Nix, we can transform this daunting task into a delightful experience.
Introducing Our Hero: The Simple JSON Parsing Example
To illustrate our journey, we'll create a C program called simple-parse-example
. This program will demonstrate basic JSON parsing using the jansson
library. Here’s what it will do:
- Create a JSON object.
- Serialize the JSON object to a string.
- Parse a JSON string back into an object.
- Print the values to prove we’ve successfully navigated the JSON landscape.
The C Program: simple-parse-example
Here’s our simple C program using the jansson
library:
// simple-parse-example.c
#include <stdio.h>
#include <jansson.h>
int main() {
// Creating a JSON object
json_t *object = json_object();
json_object_set_new(object, "name", json_string("Candlehopper"));
json_object_set_new(object, "level", json_integer(5));
json_object_set_new(object, "score", json_integer(12345));
// Serialize JSON object to string
char *json_str = json_dumps(object, JSON_INDENT(2));
if (!json_str) {
fprintf(stderr, "Error serializing JSON object.\n");
return 1;
}
printf("Serialized JSON:\n%s\n", json_str);
// Free the serialized string
free(json_str);
// JSON string to parse
const char *json_input = "{\"name\": \"Candlehopper\", \"level\": 5, \"score\": 12345}";
// Parse JSON string
json_error_t error;
json_t *parsed_object = json_loads(json_input, 0, &error);
if (!parsed_object) {
fprintf(stderr, "Error parsing JSON string: %s\n", error.text);
return 1;
}
// Extract values
json_t *name = json_object_get(parsed_object, "name");
json_t *level = json_object_get(parsed_object, "level");
json_t *score = json_object_get(parsed_object, "score");
if (json_is_string(name) && json_is_integer(level) && json_is_integer(score)) {
printf("Parsed JSON:\n");
printf("name: %s\n", json_string_value(name));
printf("level: %lld\n", json_integer_value(level));
printf("score: %lld\n", json_integer_value(score));
} else {
fprintf(stderr, "Error extracting values from JSON object.\n");
json_decref(parsed_object);
return 1;
}
// Decrement reference counts to free memory
json_decref(object);
json_decref(parsed_object);
return 0;
}
The Classic Approach: Using Makefile
First, let’s explore how to build this program using a Makefile. Here’s a simple Makefile to compile simple-parse-example
with the jansson
library:
Makefile
# the compiler to use
CC = clang
# compiler flags:
# -g adds debugging information to the executable file
# -Wall turns on most, but not all, compiler warnings
CFLAGS = -g -Wall
# files to link:
LFLAGS = -ljansson
# the names to use for both the target source files, and the output files:
TARGETS = simple-parse-example
all: $(TARGETS)
simple-parse-example: simple-parse-example.c
$(CC) $(CFLAGS) -o simple-parse-example simple-parse-example.c $(LFLAGS)
clean:
rm -f $(TARGETS)
To build the project:
make
To clean up the build files:
make clean
Pros and Cons of the Makefile Approach
Pros:
- Simple and straightforward.
- Easily integrates with various compilers and tools.
Cons:
- Manual management of dependencies.
- Non-reproducible builds.
- Platform-specific issues.
The Modern Approach: Using Nix
Now, let's switch gears and harness the power of Nix to build our project. Nix provides reproducibility and isolation, ensuring our builds are consistent across different environments.
Nix Setup
Here’s how you can set up a default.nix
file to build simple-parse-example
:
# default.nix
{ pkgs ? import <nixpkgs> {} }:
let
jansson = pkgs.jansson;
in
pkgs.stdenv.mkDerivation {
pname = "simple-parse-example";
version = "1.0";
src = ./.;
nativeBuildInputs = [ pkgs.clang ];
buildInputs = [ jansson ];
buildPhase = ''
clang -g -Wall -o simple-parse-example simple-parse-example.c -ljansson
'';
installPhase = ''
mkdir -p $out/bin
cp simple-parse-example $out/bin/
'';
meta = with pkgs.lib; {
description = "A simple program using jansson";
license = licenses.mit;
maintainers = [ maintainers.yourname ];
platforms = platforms.unix;
};
}
Building with Nix
To build your project using Nix:
-
Navigate to your project directory:
cd myproject
-
Build the project:
nix-build
-
Run the binary:
./result/bin/simple-parse-example
Pros and Cons of the Nix Approach
Pros:
- Reproducible builds.
- Easy dependency management.
- Environment isolation.
Cons:
- Steeper learning curve.
- Requires Nix installation.
Exploring Other Build Tools
While Makefiles and Nix are fantastic tools, there are other build systems and package managers worth exploring:
- CMake: A cross-platform build system that automates the configuration process. Learn more.
- vcpkg: A C/C++ library manager from Microsoft. Learn more.
- Conan: A decentralized package manager for C/C++. Learn more.
Conclusion
In this quirky adventure, we’ve explored the classic Makefile approach and the modern Nix approach to building a C program with external library dependencies. We’ve seen how Nix can bring reproducibility and ease of use to the table, making it a fantastic choice for C developers looking to streamline their build processes.
Happy coding, and may your builds always be reproducible!