Adding a Spack Package

Glossary terms: Benchmark, Package

A Spack package.py defines how to download, build, and install a benchmark. If a package already exists in the Spack Packages repository, Benchpark uses that definition. A package in repos/spack_repo/benchpark/packages/<benchmark-name>/package.py overrides the corresponding upstream package.

For the HPL example, create repos/spack_repo/benchpark/packages/hpl/package.py with the following complete contents:

  1# Copyright 2023 Lawrence Livermore National Security, LLC and other
  2# Benchpark Project Developers. See the top-level COPYRIGHT file for details.
  3#
  4# SPDX-License-Identifier: Apache-2.0
  5
  6import os
  7import platform
  8
  9from spack.package import *
 10from spack_repo.builtin.build_systems.autotools import AutotoolsPackage
 11
 12
 13class Hpl(AutotoolsPackage):
 14    """HPL is a software package that solves a (random) dense linear system
 15    in double precision (64 bits) arithmetic on distributed-memory computers.
 16    It can thus be regarded as a portable as well as freely available
 17    implementation of the High Performance Computing Linpack Benchmark."""
 18
 19    homepage = "https://www.netlib.org/benchmark/hpl/"
 20    url = "https://www.netlib.org/benchmark/hpl/hpl-2.2.tar.gz"
 21    git = "https://github.com/daboehme/HPL-caliper.git"
 22
 23    # Note: HPL uses autotools starting with 2.3
 24
 25    version("2.3", sha256="32c5c17d22330e6f2337b681aded51637fb6008d3f0eb7c277b163fadd612830")
 26    version("2.2", sha256="ac7534163a09e21a5fa763e4e16dfc119bc84043f6e6a807aba666518f8df440")
 27    version("2.3-caliper", branch="master", preferred=False)
 28
 29    variant("openmp", default=False, description="Enable OpenMP support")
 30    variant("caliper", default=False, description="Enable Caliper support")
 31
 32    depends_on("c", type="build")
 33    depends_on("cxx", type="build")
 34    depends_on("fortran", type="build")
 35
 36    depends_on("mpi@1.1:")
 37    depends_on("blas")
 38
 39    depends_on("caliper", when="+caliper")
 40    depends_on("adiak@0.4:", when="+caliper")
 41
 42    requires("@2.3-caliper", when="+caliper")
 43
 44    # 2.3 adds support for openmpi 4
 45    conflicts("^openmpi@4.0.0:", when="@:2.2")
 46
 47    parallel = False
 48
 49    arch = "{0}-{1}".format(platform.system(), platform.processor())
 50    build_targets = ["arch={0}".format(arch)]
 51
 52    def setup_build_environment(self, env):
 53        spec = self.spec
 54        if spec["mpi"].extra_attributes and "extra_link_flags" in spec["mpi"].extra_attributes:
 55            env.append_flags("LIBS", spec["mpi"].extra_attributes["extra_link_flags"])
 56
 57    @when("@:2.2")
 58    def autoreconf(self, spec, prefix):
 59        # Prevent sanity check from killing the build
 60        touch("configure")
 61
 62    @when("@:2.2")
 63    def configure(self, spec, prefix):
 64        # List of configuration options
 65        # Order is important
 66        config = []
 67
 68        # OpenMP support
 69        if "+openmp" in spec:
 70            config.append("OMP_DEFS     = {0}".format(self.compiler.openmp_flag))
 71
 72        config.extend(
 73            [
 74                # Shell
 75                "SHELL        = /bin/sh",
 76                "CD           = cd",
 77                "CP           = cp",
 78                "LN_S         = ln -fs",
 79                "MKDIR        = mkdir -p",
 80                "RM           = /bin/rm -f",
 81                "TOUCH        = touch",
 82                # Platform identifier
 83                "ARCH         = {0}".format(self.arch),
 84                # HPL Directory Structure / HPL library
 85                "TOPdir       = {0}".format(os.getcwd()),
 86                "INCdir       = $(TOPdir)/include",
 87                "BINdir       = $(TOPdir)/bin/$(ARCH)",
 88                "LIBdir       = $(TOPdir)/lib/$(ARCH)",
 89                "HPLlib       = $(LIBdir)/libhpl.a",
 90                # Message Passing library (MPI)
 91                "MPinc        = {0}".format(spec["mpi"].prefix.include),
 92                "MPlib        = -L{0}".format(spec["mpi"].prefix.lib),
 93                # Linear Algebra library (BLAS or VSIPL)
 94                "LAinc        = {0}".format(spec["blas"].prefix.include),
 95                "LAlib        = {0}".format(spec["blas"].libs.joined()),
 96                # F77 / C interface
 97                "F2CDEFS      = -DAdd_ -DF77_INTEGER=int -DStringSunStyle",
 98                # HPL includes / libraries / specifics
 99                "HPL_INCLUDES = -I$(INCdir) -I$(INCdir)/$(ARCH) " + "-I$(LAinc) -I$(MPinc)",
100                "HPL_LIBS     = $(HPLlib) $(LAlib) $(MPlib)",
101                "HPL_OPTS     = -DHPL_DETAILED_TIMING -DHPL_PROGRESS_REPORT",
102                "HPL_DEFS     = $(F2CDEFS) $(HPL_OPTS) $(HPL_INCLUDES)",
103                # Compilers / linkers - Optimization flags
104                "CC           = {0}".format(spec["mpi"].mpicc),
105                "CCNOOPT      = $(HPL_DEFS)",
106                "CCFLAGS      = $(HPL_DEFS) -O3",
107                "LINKER       = $(CC)",
108                "LINKFLAGS    = $(CCFLAGS) $(OMP_DEFS)",
109                "ARCHIVER     = ar",
110                "ARFLAGS      = r",
111                "RANLIB       = echo",
112            ]
113        )
114
115        # Write configuration options to include file
116        with open("Make.{0}".format(self.arch), "w") as makefile:
117            for var in config:
118                makefile.write("{0}\n".format(var))
119
120    @when("@2.3:")
121    def configure_args(self):
122        filter_file(r"^libs10=.*", "libs10=%s" % self.spec["blas"].libs.ld_flags, "configure")
123
124        cc, cflags, ldflags = self.spec["mpi"].mpicc, ["-O3"], []
125        if "+openmp" in self.spec:
126            cflags.append(self.compiler.openmp_flag)
127
128        if (
129            self.spec.satisfies("^intel-mkl")
130            or self.spec.satisfies("^intel-oneapi-mkl")
131            or self.spec.satisfies("^intel-parallel-studio+mkl")
132        ):
133            ldflags.append(self.spec["blas"].libs.ld_flags)
134
135        if "%aocc" in self.spec:
136            if "%aocc@3:" in self.spec:
137                ldflags.extend(["-lamdlibm", "-lm"])
138            if "%aocc@4:" in self.spec:
139                ldflags.append("-lamdalloc")
140
141        config = [f"CC={cc}", f"CFLAGS={' '.join(cflags)}", f"LDFLAGS={' '.join(ldflags)}"]
142
143        if "+caliper" in self.spec:
144            config.append("--with-adiak={0}".format(self.spec["adiak"].prefix))
145            config.append("--with-caliper={0}".format(self.spec["caliper"].prefix))
146
147        return config
148
149    @when("@:2.2")
150    def install(self, spec, prefix):
151        # Manual installation
152        install_tree(join_path("bin", self.arch), prefix.bin)
153        install_tree(join_path("lib", self.arch), prefix.lib)
154        install_tree(join_path("include", self.arch), prefix.include)
155        install_tree("man", prefix.man)
156
157    @run_after("install")
158    def copy_dat(self):
159        if self.spec.satisfies("@2.3:"):
160            # The pre-2.3 makefile would include a default HPL.dat config
161            # file in the bin directory
162            install("./testing/ptest/HPL.dat", join_path(self.prefix.bin, "HPL.dat"))

For a benchmark without an upstream package, follow the Spack Package Creation Tutorial to create and test its package.py.

Before adding the experiment, create its Ramble application if one is also missing.

Next Step