Tutorial Part 2: Using Spack Environments

In this tutorial, you’ll learn how to integrate Spack for dependency management. We’ll extend the CMake workflow from Part 1 to use Spack environments, create a Spack package for the project, and add Boost as a dependency.

Prerequisites

Adding Boost Dependency

First, let’s modify our application to use Boost.

Update main.cpp

Edit main.cpp to use Boost:

#include <iostream>
#include <boost/version.hpp>

int main() {
    std::cout << "Hello, World!" << std::endl;
    std::cout << "Using Boost "
              << BOOST_VERSION / 100000 << "."
              << BOOST_VERSION / 100 % 1000 << "."
              << BOOST_VERSION % 100 << std::endl;
    return 0;
}

Update CMakeLists.txt

Update CMakeLists.txt to find and link Boost:

cmake_minimum_required(VERSION 3.12)
project(myapp VERSION 1.0.0 LANGUAGES CXX)

find_package(Boost REQUIRED)

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE Boost::boost)

enable_testing()
add_test(NAME run_myapp COMMAND myapp)

Creating a Spack Package

Now we’ll create a Spack package for our project so Spack can manage it.

Create Spack Repository

Create a Spack repository directory in your project:

$ spack repo create . myapp

Create Package Recipe

Create spack_repo/packages/myapp/package.py:

$ spack create -r spack_repo/myapp myapp
from spack_repo.builtin.build_systems.cmake import CMakePackage
from spack.package import *

class Myapp(CMakePackage):
    """A simple application demonstrating Kessel with Spack"""

    homepage = "https://example.com/myapp"
    git = "https://github.com/myapp/myapp.git"

    version("main", branch="main")

    depends_on("boost")

Your project structure should now look like:

myapp/
├── .kessel/
│   └── workflows/
│       └── default.py
├── spack_repo/
│   └── myapp/
│       ├── repo.yaml
│       └── packages/
│           └── myapp/
│               └── package.py
├── CMakeLists.txt
└── main.cpp

Updating the Workflow

Now we’ll update the Kessel workflow to use Spack.

Extend with BuildEnvironment

Edit .kessel/workflows/default.py:

from kessel.workflows.base.spack import BuildEnvironment
from kessel.workflows.base.cmake import CMake
from kessel.workflows import *

class Default(BuildEnvironment, CMake):
    steps = ["env", "configure", "build", "test"]

    spack_env = environment("myapp-dev")
    project_spec = environment("myapp@main")

This workflow:

  • Inherits from both BuildEnvironment (for Spack) and CMake (for building)

  • Adds an env step to set up the Spack environment

  • Adds a configure step that installs dependencies and configures CMake

  • Specifies the Spack environment name (myapp-dev)

  • Specifies the project spec (myapp@main)

Running with Spack

First Run

Run the workflow with Spack:

$ kessel run

On the first run, Kessel will:

  1. Create a new Spack environment named myapp-dev (if it doesn’t exist)

  2. Add myapp@main as a root spec to the environment

  3. Register the myapp Spack repository in the environment

  4. Install Boost and other dependencies

  5. Configure the project with CMake

  6. Build and test the application

Verifying the Build

Run the built executable:

$ ./build/myapp
Hello, World!
Using Boost 1.84.0

Subsequent Runs

On subsequent runs, Kessel will use the existing environment:

$ kessel run

This time, the environment already exists and dependencies are already installed, so the workflow runs much faster.

Working with the Spack Environment

Modify Environment

You can modify the environment and re-run:

$ spack add cmake@3.27
$ kessel run

Using Different Environments

You can specify a different environment at runtime:

$ kessel run -e myapp-test

This creates or uses an environment named myapp-test.

Adding More Dependencies

Update the Spack package to add more dependencies.

Edit spack_repo/packages/myapp/package.py:

from spack.package import *

class Myapp(CMakePackage):
    """A simple application demonstrating Kessel with Spack"""

    homepage = "https://example.com/myapp"
    git = "https://github.com/myapp/myapp.git"

    version("main", branch="main")

    depends_on("boost")

    # Add more dependencies
    variant("mpi", default=False, description="Enable MPI support")
    depends_on("mpi", when="+mpi")

    def cmake_args(self):
         args = [
            self.define_from_variant("ENABLE_MPI", "mpi")
         ]
         return args

Then rebuild:

$ kessel reset
$ kessel run

Summary

In this tutorial, you:

  1. Extended your project to use Boost

  2. Created a Spack package for your project

  3. Updated the workflow to use BuildEnvironment and CMake

  4. Configured Spack environment name and project spec

  5. Ran the workflow with automatic Spack environment creation

  6. Learned to work with Spack environments

Next Steps

In Tutorial Part 3: Creating a Spack Deployment, you’ll learn how to create a Spack deployment that can be used to target multiple systems, with pre-built environments and offline build capability.