Slow build times can be a major drag on developer productivity. Waiting for your code to compile and build can interrupt your flow and waste valuable time. Lots of companies have moved to Bazel and Buck2 to deal with this issue. But for many companies that investment in migrating to Bazel is too risky or too costly. What if you could accelerate your C/C++ projects using CMake? This tutorial will guide you through setting up a blazing-fast build environment using Nativelink and Apache BuildStream for remote caching and execution. Weâll walk through a âhello, worldâ example to get you up and running in under 15 minutes.
By using them together, you can distribute your build jobs and cache their results, leading to significant speedups, especially in large projects.
Letâs start by setting up our project directory. Weâll create a structure to hold our source code, Makefile, and BuildStream configuration.
mkdir -p first-project/elements/base first-project/files/srccd first-project
Your directory structure should look like this:
first-project/âââ elements/â âââ base/ââââ files/ âââ src/
Next, letâs create our C application and the corresponding Makefile.
Create a file named hello.c
inside the files/src
directory:
/* * hello.c - a hello world program */#include <stdio.h>
int main(int argc, char *argv[]){ printf("Hello World\n"); return 0;}
Now, create a Makefile
in the same files/src
directory:
.PHONY: all
all: hellohello: hello.c $(CC) -Wall -o $@ $<
Now, letâs configure BuildStream to build our project.
First, create a project.conf
file in the root of your first-project
directory. This file defines the basic properties of your project.
# Unique project namename: first-project
# Required BuildStream versionmin-version: 2.4
# Subdirectory where elements are storedelement-path: elements
# Define an alias for our alpine tarballaliases: alpine: https://bst-integration-test-images.ams3.cdn.digitaloceanspaces.com/
Next, we need to define our base system. Weâll use a pre-built, trimmed-down Alpine Linux image. Create elements/base/alpine.bst
:
kind: importdescription: |
Alpine Linux base runtime
sources:- kind: tar # This is a post doctored, trimmed down system image # of the Alpine linux distribution. # url: alpine:integration-tests-base.v1.x86_64.tar.xz ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639
Now, create a elements/base.bst
file to create a stack with the Alpine base:
kind: stackdescription: Base stack
depends:- base/alpine.bst
####elements/hello.bst
Finally, create the element for our âhello, worldâ application in elements/hello.bst
. This element specifies how to build our application.
kind: manualdescription: |
Building manually
# Depend on the base systemdepends:- base.bst
# Stage the files/src directory for buildingsources: - kind: local path: files/src
# Now configure the commands to runconfig:
build-commands: - make hello
With our project set up, itâs time to bring in Nativelink to accelerate the build.
To tell BuildStream to use a remote execution service, create a buildstream.conf
file in your projectâs root directory:
cachedir: /tmp/buildstream
artifacts: servers: - url: http://localhost:50051 push: trueremote-execution: execution-service: url: http://localhost:50051 action-cache-service: url: http://localhost:50051 storage-service: url: http://localhost:50051
This configuration tells BuildStream to connect to a Nativelink instance running on localhost:50051
for artifact caching and remote execution.
Weâll use a script to launch the Nativelink service and then run the BuildStream build. The example below uses Nix to manage the dependencies, but you can adapt it to your environment.
Here is an example script, similar to buildstream-with-nativelink-test.nix
, that shows how to run the build.
{ nativelink, buildstream, buildbox, writeShellScriptBin,}:writeShellScriptBin "buildstream-with-nativelink-test" '' set -uo pipefail
cleanup() { local pids=$(jobs -pr) [ -n "$pids" ] && kill $pids } trap "cleanup" INT QUIT TERM EXIT
${nativelink}/bin/nativelink -- integration_tests/buildstream/buildstream_cas.json5 | tee -i integration_tests/buildstream/nativelink.log &
# TODO(palfrey): PATH is workaround for https://github.com/NixOS/nixpkgs/issues/248000#issuecomment-2934704963 bst_output=$(cd integration_tests/buildstream && PATH=${buildbox}/bin:$PATH ${buildstream}/bin/bst -c buildstream.conf build hello.bst 2>&1 | tee -i buildstream.log)
case $bst_output in *"SUCCESS Build"* ) echo "Saw a successful buildstream build" ;; *) echo 'Failed buildstream build:' echo $bst_output exit 1 ;; esac
nativelink_output=$(cat integration_tests/buildstream/nativelink.log)
case $nativelink_output in *"ERROR"* ) echo "Error in nativelink build" exit 1 ;; *) echo 'Successful nativelink build' ;; esac''
To run the build, you would execute this script. It first starts the nativelink
service in the background, then runs bst build
.
The first time you run the build, it will compile the code and store the result in the Nativelink cache. Subsequent builds will be significantly faster as the results will be fetched directly from the cache, skipping the compilation step entirely.
You have now successfully set up a project with BuildStream and Nativelink for accelerated builds with code pulled directly from Nativelinkâs production integration tests. By leveraging remote caching and execution, you can dramatically reduce build times, especially for larger and more complex projects. This allows you to iterate faster and stay in the creative flow. Finally, developers and managers alike can appreciate a developer productivity tool!
If you have any questions or want to learn more, feel free to reach out to contact@nativelink.com or get started on the Nativelink Cloud. Happy building! đ