From 7daf5823574dc18522f559a084095714cc9f3fb9 Mon Sep 17 00:00:00 2001 From: Greg Becker Date: Wed, 31 Mar 2021 18:34:52 -0700 Subject: CachedCMakePackage for using *.cmake initial config files (#19316)" Original commit message: This feature of CMake allows packages to increase reproducibility, especially between Spack- and manual builds. It also allows packages to sidestep certain parsing bugs in extremely long ``cmake`` commands, and to avoid system limits on the length of the command line. Adding: Co-authored by: Chris White This reverts commit c4f0a3cf6cd2ab282f6abf20fd72fcb4739a432a. --- lib/spack/spack/build_systems/cached_cmake.py | 249 ++++++++++++++++++++++++++ lib/spack/spack/pkgkit.py | 4 + 2 files changed, 253 insertions(+) create mode 100644 lib/spack/spack/build_systems/cached_cmake.py (limited to 'lib') diff --git a/lib/spack/spack/build_systems/cached_cmake.py b/lib/spack/spack/build_systems/cached_cmake.py new file mode 100644 index 0000000000..0704a4aa09 --- /dev/null +++ b/lib/spack/spack/build_systems/cached_cmake.py @@ -0,0 +1,249 @@ +# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +from llnl.util.filesystem import install, mkdirp +import llnl.util.tty as tty + +from spack.build_systems.cmake import CMakePackage +from spack.package import run_after + + +def cmake_cache_path(name, value, comment=""): + """Generate a string for a cmake cache variable""" + return 'set({0} "{1}" CACHE PATH "{2}")\n'.format(name, value, comment) + + +def cmake_cache_string(name, value, comment=""): + """Generate a string for a cmake cache variable""" + return 'set({0} "{1}" CACHE STRING "{2}")\n'.format(name, value, comment) + + +def cmake_cache_option(name, boolean_value, comment=""): + """Generate a string for a cmake configuration option""" + + value = "ON" if boolean_value else "OFF" + return 'set({0} {1} CACHE BOOL "{2}")\n'.format(name, value, comment) + + +class CachedCMakePackage(CMakePackage): + """Specialized class for packages built using CMake initial cache. + + This feature of CMake allows packages to increase reproducibility, + especially between Spack- and manual builds. It also allows packages to + sidestep certain parsing bugs in extremely long ``cmake`` commands, and to + avoid system limits on the length of the command line.""" + + phases = ['initconfig', 'cmake', 'build', 'install'] + + @property + def cache_name(self): + return "{0}-{1}-{2}@{3}.cmake".format( + self.name, + self.spec.architecture, + self.spec.compiler.name, + self.spec.compiler.version, + ) + + @property + def cache_path(self): + return os.path.join(self.stage.source_path, self.cache_name) + + def flag_handler(self, name, flags): + if name in ('cflags', 'cxxflags', 'cppflags', 'fflags'): + return (None, None, None) # handled in the cmake cache + return (flags, None, None) + + def initconfig_compiler_entries(self): + # This will tell cmake to use the Spack compiler wrappers when run + # through Spack, but use the underlying compiler when run outside of + # Spack + spec = self.spec + + # Fortran compiler is optional + if "FC" in os.environ: + spack_fc_entry = cmake_cache_path( + "CMAKE_Fortran_COMPILER", os.environ['FC']) + system_fc_entry = cmake_cache_path( + "CMAKE_Fortran_COMPILER", self.compiler.fc) + else: + spack_fc_entry = "# No Fortran compiler defined in spec" + system_fc_entry = "# No Fortran compiler defined in spec" + + entries = [ + "#------------------{0}".format("-" * 60), + "# Compilers", + "#------------------{0}".format("-" * 60), + "# Compiler Spec: {0}".format(spec.compiler), + "#------------------{0}".format("-" * 60), + 'if(DEFINED ENV{SPACK_CC})\n', + ' ' + cmake_cache_path( + "CMAKE_C_COMPILER", os.environ['CC']), + ' ' + cmake_cache_path( + "CMAKE_CXX_COMPILER", os.environ['CXX']), + ' ' + spack_fc_entry, + 'else()\n', + ' ' + cmake_cache_path( + "CMAKE_C_COMPILER", self.compiler.cc), + ' ' + cmake_cache_path( + "CMAKE_CXX_COMPILER", self.compiler.cxx), + ' ' + system_fc_entry, + 'endif()\n' + ] + + # use global spack compiler flags + cppflags = ' '.join(spec.compiler_flags['cppflags']) + if cppflags: + # avoid always ending up with ' ' with no flags defined + cppflags += ' ' + cflags = cppflags + ' '.join(spec.compiler_flags['cflags']) + if cflags: + entries.append(cmake_cache_string("CMAKE_C_FLAGS", cflags)) + cxxflags = cppflags + ' '.join(spec.compiler_flags['cxxflags']) + if cxxflags: + entries.append(cmake_cache_string("CMAKE_CXX_FLAGS", cxxflags)) + fflags = ' '.join(spec.compiler_flags['fflags']) + if fflags: + entries.append(cmake_cache_string("CMAKE_Fortran_FLAGS", fflags)) + + # Override XL compiler family + familymsg = ("Override to proper compiler family for XL") + if "xlf" in (self.compiler.fc or ''): # noqa: F821 + entries.append(cmake_cache_string( + "CMAKE_Fortran_COMPILER_ID", "XL", + familymsg)) + if "xlc" in self.compiler.cc: # noqa: F821 + entries.append(cmake_cache_string( + "CMAKE_C_COMPILER_ID", "XL", + familymsg)) + if "xlC" in self.compiler.cxx: # noqa: F821 + entries.append(cmake_cache_string( + "CMAKE_CXX_COMPILER_ID", "XL", + familymsg)) + + return entries + + def initconfig_mpi_entries(self): + spec = self.spec + + if "+mpi" not in spec: + return [] + + entries = [ + "#------------------{0}".format("-" * 60), + "# MPI", + "#------------------{0}\n".format("-" * 60), + ] + + entries.append(cmake_cache_path("MPI_C_COMPILER", + spec['mpi'].mpicc)) + entries.append(cmake_cache_path("MPI_CXX_COMPILER", + spec['mpi'].mpicxx)) + entries.append(cmake_cache_path("MPI_Fortran_COMPILER", + spec['mpi'].mpifc)) + + # Check for slurm + using_slurm = False + slurm_checks = ['+slurm', + 'schedulers=slurm', + 'process_managers=slurm'] + if any(spec['mpi'].satisfies(variant) for variant in slurm_checks): + using_slurm = True + + # Determine MPIEXEC + if using_slurm: + if spec['mpi'].external: + # Heuristic until we have dependents on externals + mpiexec = '/usr/bin/srun' + else: + mpiexec = os.path.join(spec['slurm'].prefix.bin, 'srun') + else: + mpiexec = os.path.join(spec['mpi'].prefix.bin, 'mpirun') + if not os.path.exists(mpiexec): + mpiexec = os.path.join(spec['mpi'].prefix.bin, 'mpiexec') + + if not os.path.exists(mpiexec): + msg = "Unable to determine MPIEXEC, %s tests may fail" % self.name + entries.append("# {0}\n".format(msg)) + tty.warn(msg) + else: + # starting with cmake 3.10, FindMPI expects MPIEXEC_EXECUTABLE + # vs the older versions which expect MPIEXEC + if self.spec["cmake"].satisfies('@3.10:'): + entries.append(cmake_cache_path("MPIEXEC_EXECUTABLE", + mpiexec)) + else: + entries.append(cmake_cache_path("MPIEXEC", mpiexec)) + + # Determine MPIEXEC_NUMPROC_FLAG + if using_slurm: + entries.append(cmake_cache_string("MPIEXEC_NUMPROC_FLAG", "-n")) + else: + entries.append(cmake_cache_string("MPIEXEC_NUMPROC_FLAG", "-np")) + + return entries + + def initconfig_hardware_entries(self): + spec = self.spec + + entries = [ + "#------------------{0}".format("-" * 60), + "# Hardware", + "#------------------{0}\n".format("-" * 60), + ] + + if '+cuda' in spec: + entries.append("#------------------{0}".format("-" * 30)) + entries.append("# Cuda") + entries.append("#------------------{0}\n".format("-" * 30)) + + cudatoolkitdir = spec['cuda'].prefix + entries.append(cmake_cache_path("CUDA_TOOLKIT_ROOT_DIR", + cudatoolkitdir)) + cudacompiler = "${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc" + entries.append(cmake_cache_path("CMAKE_CUDA_COMPILER", + cudacompiler)) + + if "+mpi" in spec: + entries.append(cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", + "${MPI_CXX_COMPILER}")) + else: + entries.append(cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", + "${CMAKE_CXX_COMPILER}")) + + return entries + + def std_initconfig_entries(self): + return [ + "#------------------{0}".format("-" * 60), + "# !!!! This is a generated file, edit at own risk !!!!", + "#------------------{0}".format("-" * 60), + "# CMake executable path: {0}".format( + self.spec['cmake'].command.path), + "#------------------{0}\n".format("-" * 60), + ] + + def initconfig(self, spec, prefix): + cache_entries = (self.std_initconfig_entries() + + self.initconfig_compiler_entries() + + self.initconfig_mpi_entries() + + self.initconfig_hardware_entries() + + self.initconfig_package_entries()) + + with open(self.cache_name, 'w') as f: + for entry in cache_entries: + f.write('%s\n' % entry) + f.write('\n') + + @property + def std_cmake_args(self): + args = super(CachedCMakePackage, self).std_cmake_args + args.extend(['-C', self.cache_path]) + return args + + @run_after('install') + def install_cmake_cache(self): + mkdirp(self.spec.prefix.share.cmake) + install(self.cache_path, self.spec.prefix.share.cmake) diff --git a/lib/spack/spack/pkgkit.py b/lib/spack/spack/pkgkit.py index 133a581e20..bc44d0b7b2 100644 --- a/lib/spack/spack/pkgkit.py +++ b/lib/spack/spack/pkgkit.py @@ -19,6 +19,10 @@ from spack.build_systems.makefile import MakefilePackage from spack.build_systems.aspell_dict import AspellDictPackage from spack.build_systems.autotools import AutotoolsPackage from spack.build_systems.cmake import CMakePackage +from spack.build_systems.cached_cmake import ( + CachedCMakePackage, cmake_cache_option, cmake_cache_path, + cmake_cache_string +) from spack.build_systems.cuda import CudaPackage from spack.build_systems.oneapi import IntelOneApiPackage from spack.build_systems.oneapi import IntelOneApiLibraryPackage -- cgit v1.2.3-70-g09d2