class HipPackage(PackageBase): """Auxiliary class which contains HIP variant, dependencies and conflicts and is meant to unify and facilitate its usage. Closely mimics CudaPackage. Maintainers: dtaller """ # https://llvm.org/docs/AMDGPUUsage.html # Possible architectures amdgpu_targets = ( 'gfx701', 'gfx801', 'gfx802', 'gfx803', 'gfx900', 'gfx906', 'gfx908', 'gfx1010', 'gfx1011', 'gfx1012', 'none' ) variant('hip', default=False, description='Enable HIP support') # possible amd gpu targets for hip builds variant('amdgpu_target', default='none', values=amdgpu_targets) depends_on('llvm-amdgpu', when='+hip') depends_on('hsa-rocr-dev', when='+hip') depends_on('hip', when='+hip') # need amd gpu type for hip builds conflicts('amdgpu_target=none', when='+hip') # Make sure non-'none' amdgpu_targets cannot be used without +hip for value in amdgpu_targets[:-1]: conflicts('~hip', when='amdgpu_target=' + value) # https://github.com/ROCm-Developer-Tools/HIP/blob/master/bin/hipcc # It seems that hip-clang does not (yet?) accept this flag, in which case # we will still need to set the HCC_AMDGPU_TARGET environment flag in the # hip package file. But I will leave this here for future development. @staticmethod def hip_flags(amdgpu_target): return '--amdgpu-target={0}'.format(amdgpu_target) # https://llvm.org/docs/AMDGPUUsage.html # Possible architectures (not including 'none' option) @staticmethod def amd_gputargets_list(): return ( 'gfx701', 'gfx801', 'gfx802', 'gfx803', 'gfx900', 'gfx906', 'gfx908', 'gfx1010', 'gfx1011', 'gfx1012' )
class ROCmPackage(PackageBase): """Auxiliary class which contains ROCm variant, dependencies and conflicts and is meant to unify and facilitate its usage. Closely mimics CudaPackage. Maintainers: dtaller """ # https://llvm.org/docs/AMDGPUUsage.html # Possible architectures amdgpu_targets = ( 'gfx701', 'gfx801', 'gfx802', 'gfx803', 'gfx900', 'gfx906', 'gfx908', 'gfx90a', 'gfx1010', 'gfx1011', 'gfx1012' ) variant('rocm', default=False, description='Enable ROCm support') # possible amd gpu targets for rocm builds variant('amdgpu_target', description='AMD GPU architecture', values=spack.variant.any_combination_of(*amdgpu_targets), when='+rocm') depends_on('llvm-amdgpu', when='+rocm') depends_on('hsa-rocr-dev', when='+rocm') depends_on('hip', when='+rocm') conflicts('^blt@:0.3.6', when='+rocm') # need amd gpu type for rocm builds conflicts('amdgpu_target=none', when='+rocm') # Make sure amdgpu_targets cannot be used without +rocm for value in amdgpu_targets: conflicts('~rocm', when='amdgpu_target=' + value) # https://github.com/ROCm-Developer-Tools/HIP/blob/master/bin/hipcc # It seems that hip-clang does not (yet?) accept this flag, in which case # we will still need to set the HCC_AMDGPU_TARGET environment flag in the # hip package file. But I will leave this here for future development. @staticmethod def hip_flags(amdgpu_target): archs = ",".join(amdgpu_target) return '--amdgpu-target={0}'.format(archs)
class PyNumpy(BuiltinPyNumpy): __doc__ = BuiltinPyNumpy.__doc__ # With the CPPFLAGS/CFLAGS/CXXFLAGS fix below then there does not appear # to be any compiler error message with NVHPC, but at least with 21.11 # then an llc process gets stuck and eventually killed when compiling # loops.c conflicts('%nvhpc') def setup_build_environment(self, env): # Otherwise we get errors related to python being %gcc: # nvc-Error-Unknown switch: -Wno-unused-result # nvc-Error-Unknown switch: -fwrapv if self.spec.satisfies('%nvhpc'): for var in ['CPPFLAGS', 'CFLAGS', 'CXXFLAGS']: env.append_flags(var, '-noswitcherror')
class CudaPackage(PackageBase): """Auxiliary class which contains CUDA variant, dependencies and conflicts and is meant to unify and facilitate its usage. Maintainers: ax3l, Rombur, davidbeckingsale """ # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list # https://developer.nvidia.com/cuda-gpus # https://en.wikipedia.org/wiki/CUDA#GPUs_supported cuda_arch_values = ( '10', '11', '12', '13', '20', '21', '30', '32', '35', '37', '50', '52', '53', '60', '61', '62', '70', '72', '75', '80', '86' ) # FIXME: keep cuda and cuda_arch separate to make usage easier until # Spack has depends_on(cuda, when='cuda_arch!=None') or alike variant('cuda', default=False, description='Build with CUDA') variant('cuda_arch', description='CUDA architecture', values=spack.variant.any_combination_of(*cuda_arch_values), when='+cuda') # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#nvcc-examples # https://llvm.org/docs/CompileCudaWithLLVM.html#compiling-cuda-code @staticmethod def cuda_flags(arch_list): return [('--generate-code arch=compute_{0},code=sm_{0} ' '--generate-code arch=compute_{0},code=compute_{0}').format(s) for s in arch_list] depends_on('cuda', when='+cuda') # CUDA version vs Architecture # https://en.wikipedia.org/wiki/CUDA#GPUs_supported # https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#deprecated-features depends_on('cuda@:6.0', when='cuda_arch=10') depends_on('cuda@:6.5', when='cuda_arch=11') depends_on('[email protected]:6.5', when='cuda_arch=12') depends_on('[email protected]:6.5', when='cuda_arch=13') depends_on('[email protected]:8.0', when='cuda_arch=20') depends_on('[email protected]:8.0', when='cuda_arch=21') depends_on('[email protected]:10.2', when='cuda_arch=30') depends_on('[email protected]:10.2', when='cuda_arch=32') depends_on('[email protected]:', when='cuda_arch=35') depends_on('[email protected]:', when='cuda_arch=37') depends_on('[email protected]:', when='cuda_arch=50') depends_on('[email protected]:', when='cuda_arch=52') depends_on('[email protected]:', when='cuda_arch=53') depends_on('[email protected]:', when='cuda_arch=60') depends_on('[email protected]:', when='cuda_arch=61') depends_on('[email protected]:', when='cuda_arch=62') depends_on('[email protected]:', when='cuda_arch=70') depends_on('[email protected]:', when='cuda_arch=72') depends_on('[email protected]:', when='cuda_arch=75') depends_on('[email protected]:', when='cuda_arch=80') depends_on('[email protected]:', when='cuda_arch=86') # From the NVIDIA install guide we know of conflicts for particular # platforms (linux, darwin), architectures (x86, powerpc) and compilers # (gcc, clang). We don't restrict %gcc and %clang conflicts to # platform=linux, since they should also apply to platform=cray, and may # apply to platform=darwin. We currently do not provide conflicts for # platform=darwin with %apple-clang. # Linux x86_64 compiler conflicts from here: # https://gist.github.com/ax3l/9489132 # GCC # According to # https://github.com/spack/spack/pull/25054#issuecomment-886531664 # these conflicts are valid independently from the architecture # minimum supported versions conflicts('%gcc@:4', when='+cuda ^[email protected]:') conflicts('%gcc@:5', when='+cuda ^[email protected]:') # maximum supported version # NOTE: # in order to not constrain future cuda version to old gcc versions, # it has been decided to use an upper bound for the latest version. # This implies that the last one in the list has to be updated at # each release of a new cuda minor version. conflicts('%gcc@10:', when='+cuda ^cuda@:11.0') conflicts('%gcc@12:', when='+cuda ^cuda@:11.6') conflicts('%clang@13:', when='+cuda ^cuda@:11.5') conflicts('%clang@14:', when='+cuda ^cuda@:11.6') # https://gist.github.com/ax3l/9489132#gistcomment-3860114 conflicts('%gcc@10', when='+cuda ^cuda@:11.4.0') conflicts('%gcc@5:', when='+cuda ^cuda@:7.5 target=x86_64:') conflicts('%gcc@6:', when='+cuda ^cuda@:8 target=x86_64:') conflicts('%gcc@7:', when='+cuda ^cuda@:9.1 target=x86_64:') conflicts('%gcc@8:', when='+cuda ^cuda@:10.0.130 target=x86_64:') conflicts('%gcc@9:', when='+cuda ^cuda@:10.2.89 target=x86_64:') conflicts('%pgi@:14.8', when='+cuda ^cuda@:7.0.27 target=x86_64:') conflicts('%pgi@:15.3,15.5:', when='+cuda ^[email protected] target=x86_64:') conflicts('%pgi@:16.2,16.0:16.3', when='+cuda ^cuda@8 target=x86_64:') conflicts('%pgi@:15,18:', when='+cuda ^[email protected]:9.1 target=x86_64:') conflicts('%pgi@:16,19:', when='+cuda ^[email protected]:10 target=x86_64:') conflicts('%pgi@:17,20:', when='+cuda ^[email protected]:10.2.89 target=x86_64:') conflicts('%pgi@:17,21:', when='+cuda ^[email protected]:11.1.0 target=x86_64:') conflicts('%clang@:3.4', when='+cuda ^cuda@:7.5 target=x86_64:') conflicts('%clang@:3.7,4:', when='+cuda ^[email protected]:9.0 target=x86_64:') conflicts('%clang@:3.7,4.1:', when='+cuda ^[email protected] target=x86_64:') conflicts('%clang@:3.7,5.1:', when='+cuda ^[email protected] target=x86_64:') conflicts('%clang@:3.7,6.1:', when='+cuda ^[email protected] target=x86_64:') conflicts('%clang@:3.7,7.1:', when='+cuda ^[email protected] target=x86_64:') conflicts('%clang@:3.7,8.1:', when='+cuda ^[email protected]:10.1.243 target=x86_64:') conflicts('%clang@:3.2,9:', when='+cuda ^[email protected] target=x86_64:') conflicts('%clang@:5', when='+cuda ^[email protected]: target=x86_64:') conflicts('%clang@10:', when='+cuda ^cuda@:11.0.3 target=x86_64:') conflicts('%clang@11:', when='+cuda ^cuda@:11.1.0 target=x86_64:') # x86_64 vs. ppc64le differ according to NVidia docs # Linux ppc64le compiler conflicts from Table from the docs below: # https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.2/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.1/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.0/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/8.0/cuda-installation-guide-linux/index.html # information prior to CUDA 9 difficult to find conflicts('%gcc@6:', when='+cuda ^cuda@:9 target=ppc64le:') conflicts('%gcc@8:', when='+cuda ^cuda@:10.0.130 target=ppc64le:') conflicts('%gcc@9:', when='+cuda ^cuda@:10.1.243 target=ppc64le:') # officially, CUDA 11.0.2 only supports the system GCC 8.3 on ppc64le conflicts('%pgi', when='+cuda ^cuda@:8 target=ppc64le:') conflicts('%pgi@:16', when='+cuda ^cuda@:9.1.185 target=ppc64le:') conflicts('%pgi@:17', when='+cuda ^cuda@:10 target=ppc64le:') conflicts('%clang@4:', when='+cuda ^cuda@:9.0.176 target=ppc64le:') conflicts('%clang@5:', when='+cuda ^cuda@:9.1 target=ppc64le:') conflicts('%clang@6:', when='+cuda ^cuda@:9.2 target=ppc64le:') conflicts('%clang@7:', when='+cuda ^[email protected] target=ppc64le:') conflicts('%[email protected]:', when='+cuda ^cuda@:10.1.105 target=ppc64le:') conflicts('%[email protected]:', when='+cuda ^cuda@:10.2.89 target=ppc64le:') conflicts('%clang@:5', when='+cuda ^[email protected]: target=ppc64le:') conflicts('%clang@10:', when='+cuda ^cuda@:11.0.2 target=ppc64le:') conflicts('%clang@11:', when='+cuda ^cuda@:11.1.0 target=ppc64le:') # Intel is mostly relevant for x86_64 Linux, even though it also # exists for Mac OS X. No information prior to CUDA 3.2 or Intel 11.1 conflicts('%intel@:11.0', when='+cuda ^cuda@:3.1') conflicts('%intel@:12.0', when='+cuda ^[email protected]:') conflicts('%intel@:13.0', when='+cuda ^[email protected]:') conflicts('%intel@:13.2', when='+cuda ^[email protected]:') conflicts('%intel@:14.9', when='+cuda ^cuda@7:') # Intel 15.x is compatible with CUDA 7 thru current CUDA conflicts('%[email protected]:', when='+cuda ^cuda@:8.0.43') conflicts('%[email protected]:', when='+cuda ^cuda@:8.0.60') conflicts('%[email protected]:', when='+cuda ^cuda@:9.9') conflicts('%[email protected]:', when='+cuda ^cuda@:10.0') conflicts('%[email protected]:', when='+cuda ^cuda@:10.1') conflicts('%[email protected]:', when='+cuda ^cuda@:11.1.0') # XL is mostly relevant for ppc64le Linux conflicts('%xl@:12,14:', when='+cuda ^cuda@:9.1') conflicts('%xl@:12,14:15,17:', when='+cuda ^[email protected]') conflicts('%xl@:12,17:', when='+cuda ^cuda@:11.1.0') # Darwin. # TODO: add missing conflicts for %apple-clang cuda@:10 conflicts('platform=darwin', when='+cuda ^[email protected]: ') # Make sure cuda_arch can not be used without +cuda for value in cuda_arch_values: conflicts('~cuda', when='cuda_arch=' + value)
class CMakePackage(PackageBase): """Specialized class for packages built using CMake For more information on the CMake build system, see: https://cmake.org/cmake/help/latest/ This class provides three phases that can be overridden: 1. :py:meth:`~.CMakePackage.cmake` 2. :py:meth:`~.CMakePackage.build` 3. :py:meth:`~.CMakePackage.install` They all have sensible defaults and for many packages the only thing necessary will be to override :py:meth:`~.CMakePackage.cmake_args`. For a finer tuning you may also override: +-----------------------------------------------+--------------------+ | **Method** | **Purpose** | +===============================================+====================+ | :py:meth:`~.CMakePackage.root_cmakelists_dir` | Location of the | | | root CMakeLists.txt| +-----------------------------------------------+--------------------+ | :py:meth:`~.CMakePackage.build_directory` | Directory where to | | | build the package | +-----------------------------------------------+--------------------+ The generator used by CMake can be specified by providing the generator attribute. Per https://cmake.org/cmake/help/git-master/manual/cmake-generators.7.html, the format is: [<secondary-generator> - ]<primary_generator>. The full list of primary and secondary generators supported by CMake may be found in the documentation for the version of CMake used; however, at this time Spack supports only the primary generators "Unix Makefiles" and "Ninja." Spack's CMake support is agnostic with respect to primary generators. Spack will generate a runtime error if the generator string does not follow the prescribed format, or if the primary generator is not supported. """ #: Phases of a CMake package phases = ['cmake', 'build', 'install'] #: This attribute is used in UI queries that need to know the build #: system base class build_system_class = 'CMakePackage' build_targets = [] # type: List[str] install_targets = ['install'] build_time_test_callbacks = ['check'] #: The build system generator to use. #: #: See ``cmake --help`` for a list of valid generators. #: Currently, "Unix Makefiles" and "Ninja" are the only generators #: that Spack supports. Defaults to "Unix Makefiles". #: #: See https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html #: for more information. generator = 'Unix Makefiles' # https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html variant('build_type', default='RelWithDebInfo', description='CMake build type', values=('Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel')) # https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html variant('ipo', default=False, description='CMake interprocedural optimization') # CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9 conflicts('+ipo', when='^cmake@:3.8', msg='+ipo is not supported by CMake < 3.9') depends_on('cmake', type='build') @property def archive_files(self): """Files to archive for packages based on CMake""" return [os.path.join(self.build_directory, 'CMakeCache.txt')] @property def root_cmakelists_dir(self): """The relative path to the directory containing CMakeLists.txt This path is relative to the root of the extracted tarball, not to the ``build_directory``. Defaults to the current directory. :return: directory containing CMakeLists.txt """ return self.stage.source_path @property def std_cmake_args(self): """Standard cmake arguments provided as a property for convenience of package writers :return: standard cmake arguments """ # standard CMake arguments std_cmake_args = CMakePackage._std_args(self) std_cmake_args += getattr(self, 'cmake_flag_args', []) return std_cmake_args @staticmethod def _std_args(pkg): """Computes the standard cmake arguments for a generic package""" try: generator = pkg.generator except AttributeError: generator = 'Unix Makefiles' # Make sure a valid generator was chosen valid_primary_generators = ['Unix Makefiles', 'Ninja'] primary_generator = _extract_primary_generator(generator) if primary_generator not in valid_primary_generators: msg = "Invalid CMake generator: '{0}'\n".format(generator) msg += "CMakePackage currently supports the following " msg += "primary generators: '{0}'".\ format("', '".join(valid_primary_generators)) raise InstallError(msg) try: build_type = pkg.spec.variants['build_type'].value except KeyError: build_type = 'RelWithDebInfo' try: ipo = pkg.spec.variants['ipo'].value except KeyError: ipo = False define = CMakePackage.define args = [ '-G', generator, define('CMAKE_INSTALL_PREFIX', pkg.prefix), define('CMAKE_BUILD_TYPE', build_type), ] # CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9 if pkg.spec.satisfies('^[email protected]:'): args.append(define('CMAKE_INTERPROCEDURAL_OPTIMIZATION', ipo)) if primary_generator == 'Unix Makefiles': args.append(define('CMAKE_VERBOSE_MAKEFILE', True)) if platform.mac_ver()[0]: args.extend([ define('CMAKE_FIND_FRAMEWORK', "LAST"), define('CMAKE_FIND_APPBUNDLE', "LAST"), ]) # Set up CMake rpath args.extend([ define('CMAKE_INSTALL_RPATH_USE_LINK_PATH', False), define('CMAKE_INSTALL_RPATH', spack.build_environment.get_rpaths(pkg)), ]) # CMake's find_package() looks in CMAKE_PREFIX_PATH first, help CMake # to find immediate link dependencies in right places: deps = [d.prefix for d in pkg.spec.dependencies(deptype=('build', 'link'))] deps = filter_system_paths(deps) args.append(define('CMAKE_PREFIX_PATH', deps)) return args @staticmethod def define(cmake_var, value): """Return a CMake command line argument that defines a variable. The resulting argument will convert boolean values to OFF/ON and lists/tuples to CMake semicolon-separated string lists. All other values will be interpreted as strings. Examples: .. code-block:: python [define('BUILD_SHARED_LIBS', True), define('CMAKE_CXX_STANDARD', 14), define('swr', ['avx', 'avx2'])] will generate the following configuration options: .. code-block:: console ["-DBUILD_SHARED_LIBS:BOOL=ON", "-DCMAKE_CXX_STANDARD:STRING=14", "-DSWR:STRING=avx;avx2] """ # Create a list of pairs. Each pair includes a configuration # option and whether or not that option is activated if isinstance(value, bool): kind = 'BOOL' value = "ON" if value else "OFF" else: kind = 'STRING' if isinstance(value, (list, tuple)): value = ";".join(str(v) for v in value) else: value = str(value) return "".join(["-D", cmake_var, ":", kind, "=", value]) def define_from_variant(self, cmake_var, variant=None): """Return a CMake command line argument from the given variant's value. The optional ``variant`` argument defaults to the lower-case transform of ``cmake_var``. This utility function is similar to :py:meth:`~.AutotoolsPackage.with_or_without`. Examples: Given a package with: .. code-block:: python variant('cxxstd', default='11', values=('11', '14'), multi=False, description='') variant('shared', default=True, description='') variant('swr', values=any_combination_of('avx', 'avx2'), description='') calling this function like: .. code-block:: python [define_from_variant('BUILD_SHARED_LIBS', 'shared'), define_from_variant('CMAKE_CXX_STANDARD', 'cxxstd'), define_from_variant('SWR')] will generate the following configuration options: .. code-block:: console ["-DBUILD_SHARED_LIBS:BOOL=ON", "-DCMAKE_CXX_STANDARD:STRING=14", "-DSWR:STRING=avx;avx2] for ``<spec-name> cxxstd=14 +shared swr=avx,avx2`` """ if variant is None: variant = cmake_var.lower() if variant not in self.variants: raise KeyError( '"{0}" is not a variant of "{1}"'.format(variant, self.name)) value = self.spec.variants[variant].value if isinstance(value, (tuple, list)): # Sort multi-valued variants for reproducibility value = sorted(value) return self.define(cmake_var, value) def flags_to_build_system_args(self, flags): """Produces a list of all command line arguments to pass the specified compiler flags to cmake. Note CMAKE does not have a cppflags option, so cppflags will be added to cflags, cxxflags, and fflags to mimic the behavior in other tools.""" # Has to be dynamic attribute due to caching setattr(self, 'cmake_flag_args', []) flag_string = '-DCMAKE_{0}_FLAGS={1}' langs = {'C': 'c', 'CXX': 'cxx', 'Fortran': 'f'} # Handle language compiler flags for lang, pre in langs.items(): flag = pre + 'flags' # cmake has no explicit cppflags support -> add it to all langs lang_flags = ' '.join(flags.get(flag, []) + flags.get('cppflags', [])) if lang_flags: self.cmake_flag_args.append(flag_string.format(lang, lang_flags)) # Cmake has different linker arguments for different build types. # We specify for each of them. if flags['ldflags']: ldflags = ' '.join(flags['ldflags']) ld_string = '-DCMAKE_{0}_LINKER_FLAGS={1}' # cmake has separate linker arguments for types of builds. for type in ['EXE', 'MODULE', 'SHARED', 'STATIC']: self.cmake_flag_args.append(ld_string.format(type, ldflags)) # CMake has libs options separated by language. Apply ours to each. if flags['ldlibs']: libs_flags = ' '.join(flags['ldlibs']) libs_string = '-DCMAKE_{0}_STANDARD_LIBRARIES={1}' for lang in langs: self.cmake_flag_args.append(libs_string.format(lang, libs_flags)) @property def build_dirname(self): """Returns the directory name to use when building the package :return: name of the subdirectory for building the package """ return 'spack-build-%s' % self.spec.dag_hash(7) @property def build_directory(self): """Returns the directory to use when building the package :return: directory where to build the package """ return os.path.join(self.stage.path, self.build_dirname) def cmake_args(self): """Produces a list containing all the arguments that must be passed to cmake, except: * CMAKE_INSTALL_PREFIX * CMAKE_BUILD_TYPE which will be set automatically. :return: list of arguments for cmake """ return [] def cmake(self, spec, prefix): """Runs ``cmake`` in the build directory""" options = self.std_cmake_args options += self.cmake_args() options.append(os.path.abspath(self.root_cmakelists_dir)) with working_dir(self.build_directory, create=True): inspect.getmodule(self).cmake(*options) def build(self, spec, prefix): """Make the build targets""" with working_dir(self.build_directory): if self.generator == 'Unix Makefiles': inspect.getmodule(self).make(*self.build_targets) elif self.generator == 'Ninja': self.build_targets.append("-v") inspect.getmodule(self).ninja(*self.build_targets) def install(self, spec, prefix): """Make the install targets""" with working_dir(self.build_directory): if self.generator == 'Unix Makefiles': inspect.getmodule(self).make(*self.install_targets) elif self.generator == 'Ninja': inspect.getmodule(self).ninja(*self.install_targets) run_after('build')(PackageBase._run_default_build_time_test_callbacks) def check(self): """Searches the CMake-generated Makefile for the target ``test`` and runs it if found. """ with working_dir(self.build_directory): if self.generator == 'Unix Makefiles': self._if_make_target_execute('test', jobs_env='CTEST_PARALLEL_LEVEL') self._if_make_target_execute('check') elif self.generator == 'Ninja': self._if_ninja_target_execute('test', jobs_env='CTEST_PARALLEL_LEVEL') self._if_ninja_target_execute('check') # Check that self.prefix is there after installation run_after('install')(PackageBase.sanity_check_prefix)
class CudaPackage(PackageBase): """Auxiliary class which contains CUDA variant, dependencies and conflicts and is meant to unify and facilitate its usage. Maintainers: ax3l, Rombur """ # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list # https://developer.nvidia.com/cuda-gpus # https://en.wikipedia.org/wiki/CUDA#GPUs_supported cuda_arch_values = ('10', '11', '12', '13', '20', '21', '30', '32', '35', '37', '50', '52', '53', '60', '61', '62', '70', '72', '75', '80', '86') # FIXME: keep cuda and cuda_arch separate to make usage easier until # Spack has depends_on(cuda, when='cuda_arch!=None') or alike variant('cuda', default=False, description='Build with CUDA') variant('cuda_arch', description='CUDA architecture', values=spack.variant.any_combination_of(*cuda_arch_values)) # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#nvcc-examples # https://llvm.org/docs/CompileCudaWithLLVM.html#compiling-cuda-code @staticmethod def cuda_flags(arch_list): return [('--generate-code arch=compute_{0},code=sm_{0} ' '--generate-code arch=compute_{0},code=compute_{0}').format(s) for s in arch_list] depends_on('cuda', when='+cuda') # CUDA version vs Architecture # https://en.wikipedia.org/wiki/CUDA#GPUs_supported # https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#deprecated-features depends_on('cuda@:6.0', when='cuda_arch=10') depends_on('cuda@:6.5', when='cuda_arch=11') depends_on('[email protected]:6.5', when='cuda_arch=12') depends_on('[email protected]:6.5', when='cuda_arch=13') depends_on('[email protected]:8.0', when='cuda_arch=20') depends_on('[email protected]:8.0', when='cuda_arch=21') depends_on('[email protected]:10.2', when='cuda_arch=30') depends_on('[email protected]:10.2', when='cuda_arch=32') depends_on('[email protected]:', when='cuda_arch=35') depends_on('[email protected]:', when='cuda_arch=37') depends_on('[email protected]:', when='cuda_arch=50') depends_on('[email protected]:', when='cuda_arch=52') depends_on('[email protected]:', when='cuda_arch=53') depends_on('[email protected]:', when='cuda_arch=60') depends_on('[email protected]:', when='cuda_arch=61') depends_on('[email protected]:', when='cuda_arch=62') depends_on('[email protected]:', when='cuda_arch=70') depends_on('[email protected]:', when='cuda_arch=72') depends_on('[email protected]:', when='cuda_arch=75') depends_on('[email protected]:', when='cuda_arch=80') depends_on('[email protected]:', when='cuda_arch=86') # There are at least three cases to be aware of for compiler conflicts # 1. Linux x86_64 # 2. Linux ppc64le # 3. Mac OS X # CUDA-compiler conflicts are version-to-version specific and are # difficult to express with the current Spack conflict syntax # Linux x86_64 compiler conflicts from here: # https://gist.github.com/ax3l/9489132 arch_platform = ' target=x86_64: platform=linux' conflicts('%gcc@5:', when='+cuda ^cuda@:7.5' + arch_platform) conflicts('%gcc@6:', when='+cuda ^cuda@:8' + arch_platform) conflicts('%gcc@7:', when='+cuda ^cuda@:9.1' + arch_platform) conflicts('%gcc@8:', when='+cuda ^cuda@:10.0.130' + arch_platform) conflicts('%gcc@9:', when='+cuda ^cuda@:10.2.89' + arch_platform) conflicts('%gcc@:4', when='+cuda ^[email protected]:' + arch_platform) conflicts('%gcc@10:', when='+cuda ^cuda@:11.0.2' + arch_platform) conflicts('%gcc@11:', when='+cuda ^cuda@:11.1.0' + arch_platform) conflicts('%pgi@:14.8', when='+cuda ^cuda@:7.0.27' + arch_platform) conflicts('%pgi@:15.3,15.5:', when='+cuda ^[email protected]' + arch_platform) conflicts('%pgi@:16.2,16.0:16.3', when='+cuda ^cuda@8' + arch_platform) conflicts('%pgi@:15,18:', when='+cuda ^[email protected]:9.1' + arch_platform) conflicts('%pgi@:16,19:', when='+cuda ^[email protected]:10' + arch_platform) conflicts('%pgi@:17,20:', when='+cuda ^[email protected]:10.2.89' + arch_platform) conflicts('%pgi@:17,21:', when='+cuda ^[email protected]:11.1.0' + arch_platform) conflicts('%clang@:3.4', when='+cuda ^cuda@:7.5' + arch_platform) conflicts('%clang@:3.7,4:', when='+cuda ^[email protected]:9.0' + arch_platform) conflicts('%clang@:3.7,4.1:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:3.7,5.1:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:3.7,6.1:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:3.7,7.1:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:3.7,8.1:', when='+cuda ^[email protected]:10.1.243' + arch_platform) conflicts('%clang@:3.2,9:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:5', when='+cuda ^[email protected]:' + arch_platform) conflicts('%clang@10:', when='+cuda ^cuda@:11.0.2' + arch_platform) conflicts('%clang@11:', when='+cuda ^cuda@:11.1.0' + arch_platform) # x86_64 vs. ppc64le differ according to NVidia docs # Linux ppc64le compiler conflicts from Table from the docs below: # https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.2/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.1/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.0/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/8.0/cuda-installation-guide-linux/index.html arch_platform = ' target=ppc64le: platform=linux' # information prior to CUDA 9 difficult to find conflicts('%gcc@6:', when='+cuda ^cuda@:9' + arch_platform) conflicts('%gcc@8:', when='+cuda ^cuda@:10.0.130' + arch_platform) conflicts('%gcc@9:', when='+cuda ^cuda@:10.1.243' + arch_platform) # officially, CUDA 11.0.2 only supports the system GCC 8.3 on ppc64le conflicts('%gcc@:4', when='+cuda ^[email protected]:' + arch_platform) conflicts('%gcc@10:', when='+cuda ^cuda@:11.0.2' + arch_platform) conflicts('%gcc@11:', when='+cuda ^cuda@:11.1.0' + arch_platform) conflicts('%pgi', when='+cuda ^cuda@:8' + arch_platform) conflicts('%pgi@:16', when='+cuda ^cuda@:9.1.185' + arch_platform) conflicts('%pgi@:17', when='+cuda ^cuda@:10' + arch_platform) conflicts('%clang@4:', when='+cuda ^cuda@:9.0.176' + arch_platform) conflicts('%clang@5:', when='+cuda ^cuda@:9.1' + arch_platform) conflicts('%clang@6:', when='+cuda ^cuda@:9.2' + arch_platform) conflicts('%clang@7:', when='+cuda ^[email protected]' + arch_platform) conflicts('%[email protected]:', when='+cuda ^cuda@:10.1.105' + arch_platform) conflicts('%[email protected]:', when='+cuda ^cuda@:10.2.89' + arch_platform) conflicts('%clang@:5', when='+cuda ^[email protected]:' + arch_platform) conflicts('%clang@10:', when='+cuda ^cuda@:11.0.2' + arch_platform) conflicts('%clang@11:', when='+cuda ^cuda@:11.1.0' + arch_platform) # Intel is mostly relevant for x86_64 Linux, even though it also # exists for Mac OS X. No information prior to CUDA 3.2 or Intel 11.1 conflicts('%intel@:11.0', when='+cuda ^cuda@:3.1') conflicts('%intel@:12.0', when='+cuda ^[email protected]:') conflicts('%intel@:13.0', when='+cuda ^[email protected]:') conflicts('%intel@:13.2', when='+cuda ^[email protected]:') conflicts('%intel@:14.9', when='+cuda ^cuda@7:') # Intel 15.x is compatible with CUDA 7 thru current CUDA conflicts('%[email protected]:', when='+cuda ^cuda@:8.0.43') conflicts('%[email protected]:', when='+cuda ^cuda@:8.0.60') conflicts('%[email protected]:', when='+cuda ^cuda@:9.9') conflicts('%[email protected]:', when='+cuda ^cuda@:10.0') conflicts('%[email protected]:', when='+cuda ^cuda@:10.1') conflicts('%[email protected]:', when='+cuda ^cuda@:11.1.0') # XL is mostly relevant for ppc64le Linux conflicts('%xl@:12,14:', when='+cuda ^cuda@:9.1') conflicts('%xl@:12,14:15,17:', when='+cuda ^[email protected]') conflicts('%xl@:12,17:', when='+cuda ^cuda@:11.1.0') # Mac OS X # platform = ' platform=darwin' # Apple XCode clang vs. LLVM clang are difficult to specify # with spack syntax. Xcode clang name is `[email protected]` # which precludes ranges being specified. We have proposed # rename XCode clang to `[email protected]` or even # `[email protected] as a possible fix. # Compiler conflicts will be eventual taken from here: # https://docs.nvidia.com/cuda/cuda-installation-guide-mac-os-x/index.html#abstract conflicts('platform=darwin', when='+cuda ^[email protected]:') # Make sure cuda_arch can not be used without +cuda for value in cuda_arch_values: conflicts('~cuda', when='cuda_arch=' + value)
class CudaPackage(PackageBase): """Auxiliary class which contains CUDA variant, dependencies and conflicts and is meant to unify and facilitate its usage. """ # FIXME: keep cuda and cuda_arch separate to make usage easier untill # Spack has depends_on(cuda, when='cuda_arch!=None') or alike variant('cuda', default=False, description='Build with CUDA') # see http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list # https://developer.nvidia.com/cuda-gpus variant('cuda_arch', default=None, description='CUDA architecture', values=('20', '30', '32', '35', '50', '52', '53', '60', '61', '62', '70'), multi=True) # see http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#nvcc-examples # and http://llvm.org/docs/CompileCudaWithLLVM.html#compiling-cuda-code @staticmethod def cuda_flags(arch_list): return [('--generate-code arch=compute_{0},code=sm_{0} ' '--generate-code arch=compute_{0},code=compute_{0}').format(s) for s in arch_list] depends_on("cuda@7:", when='+cuda') # CUDA version vs Architecture depends_on("cuda@8:", when='cuda_arch=60') depends_on("cuda@8:", when='cuda_arch=61') depends_on("cuda@8:", when='cuda_arch=62') depends_on("cuda@9:", when='cuda_arch=70') depends_on('cuda@:8', when='cuda_arch=20') # Compiler conflicts: # https://gist.github.com/ax3l/9489132 conflicts('%gcc@5:', when='+cuda ^cuda@:7.5') conflicts('%gcc@6:', when='+cuda ^cuda@:8') conflicts('%gcc@7:', when='+cuda ^cuda@:9.1') conflicts('%gcc@8:', when='+cuda ^cuda@:9.99') if (platform.system() != "Darwin"): conflicts('%clang@:3.4,3.7:', when='+cuda ^[email protected]') conflicts('%clang@:3.7,4:', when='+cuda ^cuda@8:9.0') conflicts('%clang@:3.7,5:', when='+cuda ^[email protected]') conflicts('%clang@:3.7,6:', when='+cuda ^[email protected]') conflicts('%intel@:14,16:', when='+cuda ^[email protected]') conflicts('%intel@:14,17:', when='+cuda ^[email protected]') conflicts('%intel@:14,18:', when='+cuda ^[email protected]:9') # Make sure cuda_arch can not be used without +cuda conflicts('~cuda', when='cuda_arch=20') conflicts('~cuda', when='cuda_arch=30') conflicts('~cuda', when='cuda_arch=32') conflicts('~cuda', when='cuda_arch=35') conflicts('~cuda', when='cuda_arch=50') conflicts('~cuda', when='cuda_arch=52') conflicts('~cuda', when='cuda_arch=53') conflicts('~cuda', when='cuda_arch=60') conflicts('~cuda', when='cuda_arch=61') conflicts('~cuda', when='cuda_arch=62') conflicts('~cuda', when='cuda_arch=70')
class MakefilePackage(PackageBase): """Specialized class for packages that are built using editable Makefiles This class provides three phases that can be overridden: 1. :py:meth:`~.MakefilePackage.edit` 2. :py:meth:`~.MakefilePackage.build` 3. :py:meth:`~.MakefilePackage.install` It is usually necessary to override the :py:meth:`~.MakefilePackage.edit` phase, while :py:meth:`~.MakefilePackage.build` and :py:meth:`~.MakefilePackage.install` have sensible defaults. For a finer tuning you may override: +-----------------------------------------------+--------------------+ | **Method** | **Purpose** | +===============================================+====================+ | :py:attr:`~.MakefilePackage.build_targets` | Specify ``make`` | | | targets for the | | | build phase | +-----------------------------------------------+--------------------+ | :py:attr:`~.MakefilePackage.install_targets` | Specify ``make`` | | | targets for the | | | install phase | +-----------------------------------------------+--------------------+ | :py:meth:`~.MakefilePackage.build_directory` | Directory where the| | | Makefile is located| +-----------------------------------------------+--------------------+ """ #: Phases of a package that is built with an hand-written Makefile phases = ['edit', 'build', 'install'] #: This attribute is used in UI queries that need to know the build #: system base class build_system_class = 'MakefilePackage' #: Targets for ``make`` during the :py:meth:`~.MakefilePackage.build` #: phase build_targets = [] # type: List[str] #: Targets for ``make`` during the :py:meth:`~.MakefilePackage.install` #: phase install_targets = ['install'] conflicts('platform=windows') #: Callback names for build-time test build_time_test_callbacks = ['check'] #: Callback names for install-time test install_time_test_callbacks = ['installcheck'] @property def build_directory(self): """Returns the directory containing the main Makefile :return: build directory """ return self.stage.source_path def edit(self, spec, prefix): """Edits the Makefile before calling make. This phase cannot be defaulted. """ tty.msg('Using default implementation: skipping edit phase.') def build(self, spec, prefix): """Calls make, passing :py:attr:`~.MakefilePackage.build_targets` as targets. """ with working_dir(self.build_directory): inspect.getmodule(self).make(*self.build_targets) def install(self, spec, prefix): """Calls make, passing :py:attr:`~.MakefilePackage.install_targets` as targets. """ with working_dir(self.build_directory): inspect.getmodule(self).make(*self.install_targets) run_after('build')(PackageBase._run_default_build_time_test_callbacks) def check(self): """Searches the Makefile for targets ``test`` and ``check`` and runs them if found. """ with working_dir(self.build_directory): self._if_make_target_execute('test') self._if_make_target_execute('check') run_after('install')(PackageBase._run_default_install_time_test_callbacks) def installcheck(self): """Searches the Makefile for an ``installcheck`` target and runs it if found. """ with working_dir(self.build_directory): self._if_make_target_execute('installcheck') # Check that self.prefix is there after installation run_after('install')(PackageBase.sanity_check_prefix) # On macOS, force rpaths for shared library IDs and remove duplicate rpaths run_after('install')(PackageBase.apply_macos_rpath_fixups)
class AutotoolsPackage(PackageBase): """Specialized class for packages built using GNU Autotools. This class provides four phases that can be overridden: 1. :py:meth:`~.AutotoolsPackage.autoreconf` 2. :py:meth:`~.AutotoolsPackage.configure` 3. :py:meth:`~.AutotoolsPackage.build` 4. :py:meth:`~.AutotoolsPackage.install` They all have sensible defaults and for many packages the only thing necessary will be to override the helper method :meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args`. For a finer tuning you may also override: +-----------------------------------------------+--------------------+ | **Method** | **Purpose** | +===============================================+====================+ | :py:attr:`~.AutotoolsPackage.build_targets` | Specify ``make`` | | | targets for the | | | build phase | +-----------------------------------------------+--------------------+ | :py:attr:`~.AutotoolsPackage.install_targets` | Specify ``make`` | | | targets for the | | | install phase | +-----------------------------------------------+--------------------+ | :py:meth:`~.AutotoolsPackage.check` | Run build time | | | tests if required | +-----------------------------------------------+--------------------+ """ #: Phases of a GNU Autotools package phases = ['autoreconf', 'configure', 'build', 'install'] #: This attribute is used in UI queries that need to know the build #: system base class build_system_class = 'AutotoolsPackage' @property def patch_config_files(self): """ Whether or not to update old ``config.guess`` and ``config.sub`` files distributed with the tarball. This currently only applies to ``ppc64le:``, ``aarch64:``, and ``riscv64`` target architectures. The substitutes are taken from the ``gnuconfig`` package, which is automatically added as a build dependency for these architectures. In case system versions of these config files are required, the ``gnuconfig`` package can be marked external with a prefix pointing to the directory containing the system ``config.guess`` and ``config.sub`` files. """ return (self.spec.satisfies('target=ppc64le:') or self.spec.satisfies('target=aarch64:') or self.spec.satisfies('target=riscv64:')) #: Whether or not to update ``libtool`` #: (currently only for Arm/Clang/Fujitsu compilers) patch_libtool = True #: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.build` #: phase build_targets = [] # type: List[str] #: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.install` #: phase install_targets = ['install'] #: Callback names for build-time test build_time_test_callbacks = ['check'] #: Callback names for install-time test install_time_test_callbacks = ['installcheck'] #: Set to true to force the autoreconf step even if configure is present force_autoreconf = False #: Options to be passed to autoreconf when using the default implementation autoreconf_extra_args = [] # type: List[str] #: If False deletes all the .la files in the prefix folder #: after the installation. If True instead it installs them. install_libtool_archives = False depends_on('gnuconfig', type='build', when='target=ppc64le:') depends_on('gnuconfig', type='build', when='target=aarch64:') depends_on('gnuconfig', type='build', when='target=riscv64:') conflicts('platform=windows') @property def _removed_la_files_log(self): """File containing the list of remove libtool archives""" build_dir = self.build_directory if not os.path.isabs(self.build_directory): build_dir = os.path.join(self.stage.path, build_dir) return os.path.join(build_dir, 'removed_la_files.txt') @property def archive_files(self): """Files to archive for packages based on autotools""" files = [os.path.join(self.build_directory, 'config.log')] if not self.install_libtool_archives: files.append(self._removed_la_files_log) return files @run_after('autoreconf') def _do_patch_config_files(self): """Some packages ship with older config.guess/config.sub files and need to have these updated when installed on a newer architecture. In particular, config.guess fails for PPC64LE for version prior to a 2013-06-10 build date (automake 1.13.4) and for ARM (aarch64) and RISC-V (riscv64). """ if not self.patch_config_files: return # TODO: Expand this to select the 'config.sub'-compatible architecture # for each platform (e.g. 'config.sub' doesn't accept 'power9le', but # does accept 'ppc64le'). if self.spec.satisfies('target=ppc64le:'): config_arch = 'ppc64le' elif self.spec.satisfies('target=aarch64:'): config_arch = 'aarch64' elif self.spec.satisfies('target=riscv64:'): config_arch = 'riscv64' else: config_arch = 'local' def runs_ok(script_abs_path): # Construct the list of arguments for the call additional_args = {'config.sub': [config_arch]} script_name = os.path.basename(script_abs_path) args = [script_abs_path] + additional_args.get(script_name, []) try: check_call(args, stdout=PIPE, stderr=PIPE) except Exception as e: tty.debug(e) return False return True # Get the list of files that needs to be patched to_be_patched = fs.find(self.stage.path, files=['config.sub', 'config.guess']) to_be_patched = [f for f in to_be_patched if not runs_ok(f)] # If there are no files to be patched, return early if not to_be_patched: return # Otherwise, require `gnuconfig` to be a build dependency self._require_build_deps(pkgs=['gnuconfig'], spec=self.spec, err="Cannot patch config files") # Get the config files we need to patch (config.sub / config.guess). to_be_found = list(set(os.path.basename(f) for f in to_be_patched)) gnuconfig = self.spec['gnuconfig'] gnuconfig_dir = gnuconfig.prefix # An external gnuconfig may not not have a prefix. if gnuconfig_dir is None: raise InstallError( "Spack could not find substitutes for GNU config " "files because no prefix is available for the " "`gnuconfig` package. Make sure you set a prefix " "path instead of modules for external `gnuconfig`.") candidates = fs.find(gnuconfig_dir, files=to_be_found, recursive=False) # For external packages the user may have specified an incorrect prefix. # otherwise the installation is just corrupt. if not candidates: msg = ("Spack could not find `config.guess` and `config.sub` " "files in the `gnuconfig` prefix `{0}`. This means the " "`gnuconfig` package is broken").format(gnuconfig_dir) if gnuconfig.external: msg += ( " or the `gnuconfig` package prefix is misconfigured as" " an external package") raise InstallError(msg) # Filter working substitutes candidates = [f for f in candidates if runs_ok(f)] substitutes = {} for candidate in candidates: config_file = os.path.basename(candidate) substitutes[config_file] = candidate to_be_found.remove(config_file) # Check that we found everything we needed if to_be_found: msg = """\ Spack could not find working replacements for the following autotools config files: {0}. To resolve this problem, please try the following: 1. Try to rebuild with `patch_config_files = False` in the package `{1}`, to rule out that Spack tries to replace config files not used by the build. 2. Verify that the `gnuconfig` package is up-to-date. 3. On some systems you need to use system-provided `config.guess` and `config.sub` files. In this case, mark `gnuconfig` as an non-buildable external package, and set the prefix to the directory containing the `config.guess` and `config.sub` files. """ raise InstallError(msg.format(', '.join(to_be_found), self.name)) # Copy the good files over the bad ones for abs_path in to_be_patched: name = os.path.basename(abs_path) mode = os.stat(abs_path).st_mode os.chmod(abs_path, stat.S_IWUSR) fs.copy(substitutes[name], abs_path) os.chmod(abs_path, mode) @run_before('configure') def _set_autotools_environment_variables(self): """Many autotools builds use a version of mknod.m4 that fails when running as root unless FORCE_UNSAFE_CONFIGURE is set to 1. We set this to 1 and expect the user to take responsibility if they are running as root. They have to anyway, as this variable doesn't actually prevent configure from doing bad things as root. Without it, configure just fails halfway through, but it can still run things *before* this check. Forcing this just removes a nuisance -- this is not circumventing any real protection. """ os.environ["FORCE_UNSAFE_CONFIGURE"] = "1" @run_after('configure') def _do_patch_libtool(self): """If configure generates a "libtool" script that does not correctly detect the compiler (and patch_libtool is set), patch in the correct flags for the Arm, Clang/Flang, and Fujitsu compilers.""" # Exit early if we are required not to patch libtool if not self.patch_libtool: return for libtool_path in fs.find(self.build_directory, 'libtool', recursive=True): self._patch_libtool(libtool_path) def _patch_libtool(self, libtool_path): if self.spec.satisfies('%arm')\ or self.spec.satisfies('%clang')\ or self.spec.satisfies('%fj'): fs.filter_file('wl=""\n', 'wl="-Wl,"\n', libtool_path) fs.filter_file( 'pic_flag=""\n', 'pic_flag="{0}"\n'.format(self.compiler.cc_pic_flag), libtool_path) if self.spec.satisfies('%fj'): fs.filter_file('-nostdlib', '', libtool_path) rehead = r'/\S*/' objfile = [ 'fjhpctag.o', 'fjcrt0.o', 'fjlang08.o', 'fjomp.o', 'crti.o', 'crtbeginS.o', 'crtendS.o' ] for o in objfile: fs.filter_file(rehead + o, '', libtool_path) @property def configure_directory(self): """Returns the directory where 'configure' resides. :return: directory where to find configure """ return self.stage.source_path @property def configure_abs_path(self): # Absolute path to configure configure_abs_path = os.path.join( os.path.abspath(self.configure_directory), 'configure') return configure_abs_path @property def build_directory(self): """Override to provide another place to build the package""" return self.configure_directory @run_before('autoreconf') def delete_configure_to_force_update(self): if self.force_autoreconf: force_remove(self.configure_abs_path) def _require_build_deps(self, pkgs, spec, err): """Require `pkgs` to be direct build dependencies of `spec`. Raises a RuntimeError with a helpful error messages when any dep is missing.""" build_deps = [d.name for d in spec.dependencies(deptype='build')] missing_deps = [x for x in pkgs if x not in build_deps] if not missing_deps: return # Raise an exception on missing deps. msg = ("{0}: missing dependencies: {1}.\n\nPlease add " "the following lines to the package:\n\n".format( err, ", ".join(missing_deps))) for dep in missing_deps: msg += ( " depends_on('{0}', type='build', when='@{1}')\n".format( dep, spec.version)) msg += "\nUpdate the version (when='@{0}') as needed.".format( spec.version) raise RuntimeError(msg) def autoreconf(self, spec, prefix): """Not needed usually, configure should be already there""" # If configure exists nothing needs to be done if os.path.exists(self.configure_abs_path): return # Else try to regenerate it, which reuquires a few build dependencies self._require_build_deps(pkgs=['autoconf', 'automake', 'libtool'], spec=spec, err="Cannot generate configure") tty.msg('Configure script not found: trying to generate it') tty.warn('*********************************************************') tty.warn('* If the default procedure fails, consider implementing *') tty.warn('* a custom AUTORECONF phase in the package *') tty.warn('*********************************************************') with working_dir(self.configure_directory): m = inspect.getmodule(self) # This line is what is needed most of the time # --install, --verbose, --force autoreconf_args = ['-ivf'] autoreconf_args += self.autoreconf_search_path_args autoreconf_args += self.autoreconf_extra_args m.autoreconf(*autoreconf_args) @property def autoreconf_search_path_args(self): """Arguments to autoreconf to modify the search paths""" search_path_args = [] for dep in self.spec.dependencies(deptype='build'): if os.path.exists(dep.prefix.share.aclocal): search_path_args.extend(['-I', dep.prefix.share.aclocal]) return search_path_args @run_after('autoreconf') def set_configure_or_die(self): """Checks the presence of a ``configure`` file after the autoreconf phase. If it is found sets a module attribute appropriately, otherwise raises an error. :raises RuntimeError: if a configure script is not found in :py:meth:`~AutotoolsPackage.configure_directory` """ # Check if a configure script is there. If not raise a RuntimeError. if not os.path.exists(self.configure_abs_path): msg = 'configure script not found in {0}' raise RuntimeError(msg.format(self.configure_directory)) # Monkey-patch the configure script in the corresponding module inspect.getmodule(self).configure = Executable(self.configure_abs_path) def configure_args(self): """Produces a list containing all the arguments that must be passed to configure, except ``--prefix`` which will be pre-pended to the list. :return: list of arguments for configure """ return [] def flags_to_build_system_args(self, flags): """Produces a list of all command line arguments to pass specified compiler flags to configure.""" # Has to be dynamic attribute due to caching. setattr(self, 'configure_flag_args', []) for flag, values in flags.items(): if values: values_str = '{0}={1}'.format(flag.upper(), ' '.join(values)) self.configure_flag_args.append(values_str) # Spack's fflags are meant for both F77 and FC, therefore we # additionaly set FCFLAGS if required. values = flags.get('fflags', None) if values: values_str = 'FCFLAGS={0}'.format(' '.join(values)) self.configure_flag_args.append(values_str) def configure(self, spec, prefix): """Runs configure with the arguments specified in :meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args` and an appropriately set prefix. """ options = getattr(self, 'configure_flag_args', []) options += ['--prefix={0}'.format(prefix)] options += self.configure_args() with working_dir(self.build_directory, create=True): inspect.getmodule(self).configure(*options) def setup_build_environment(self, env): if (self.spec.platform == 'darwin' and macos_version() >= Version('11')): # Many configure files rely on matching '10.*' for macOS version # detection and fail to add flags if it shows as version 11. env.set('MACOSX_DEPLOYMENT_TARGET', '10.16') def build(self, spec, prefix): """Makes the build targets specified by :py:attr:``~.AutotoolsPackage.build_targets`` """ # See https://autotools.io/automake/silent.html params = ['V=1'] params += self.build_targets with working_dir(self.build_directory): inspect.getmodule(self).make(*params) def install(self, spec, prefix): """Makes the install targets specified by :py:attr:``~.AutotoolsPackage.install_targets`` """ with working_dir(self.build_directory): inspect.getmodule(self).make(*self.install_targets) run_after('build')(PackageBase._run_default_build_time_test_callbacks) def check(self): """Searches the Makefile for targets ``test`` and ``check`` and runs them if found. """ with working_dir(self.build_directory): self._if_make_target_execute('test') self._if_make_target_execute('check') def _activate_or_not(self, name, activation_word, deactivation_word, activation_value=None, variant=None): """This function contains the current implementation details of :meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without` and :meth:`~spack.build_systems.autotools.AutotoolsPackage.enable_or_disable`. Args: name (str): name of the option that is being activated or not activation_word (str): the default activation word ('with' in the case of ``with_or_without``) deactivation_word (str): the default deactivation word ('without' in the case of ``with_or_without``) activation_value (typing.Callable): callable that accepts a single value. This value is either one of the allowed values for a multi-valued variant or the name of a bool-valued variant. Returns the parameter to be used when the value is activated. The special value 'prefix' can also be assigned and will return ``spec[name].prefix`` as activation parameter. variant (str): name of the variant that is being processed (if different from option name) Examples: Given a package with: .. code-block:: python variant('foo', values=('x', 'y'), description='') variant('bar', default=True, description='') variant('ba_z', default=True, description='') calling this function like: .. code-block:: python _activate_or_not( 'foo', 'with', 'without', activation_value='prefix' ) _activate_or_not('bar', 'with', 'without') _activate_or_not('ba-z', 'with', 'without', variant='ba_z') will generate the following configuration options: .. code-block:: console --with-x=<prefix-to-x> --without-y --with-bar --with-ba-z for ``<spec-name> foo=x +bar`` Note: returns an empty list when the variant is conditional and its condition is not met. Returns: list: list of strings that corresponds to the activation/deactivation of the variant that has been processed Raises: KeyError: if name is not among known variants """ spec = self.spec args = [] if activation_value == 'prefix': activation_value = lambda x: spec[x].prefix variant = variant or name # Defensively look that the name passed as argument is among # variants if variant not in self.variants: msg = '"{0}" is not a variant of "{1}"' raise KeyError(msg.format(variant, self.name)) if variant not in spec.variants: return [] # Create a list of pairs. Each pair includes a configuration # option and whether or not that option is activated variant_desc, _ = self.variants[variant] if set(variant_desc.values) == set((True, False)): # BoolValuedVariant carry information about a single option. # Nonetheless, for uniformity of treatment we'll package them # in an iterable of one element. condition = '+{name}'.format(name=variant) options = [(name, condition in spec)] else: condition = '{variant}={value}' # "feature_values" is used to track values which correspond to # features which can be enabled or disabled as understood by the # package's build system. It excludes values which have special # meanings and do not correspond to features (e.g. "none") feature_values = getattr(variant_desc.values, 'feature_values', None) or variant_desc.values options = [(value, condition.format(variant=variant, value=value) in spec) for value in feature_values] # For each allowed value in the list of values for option_value, activated in options: # Search for an override in the package for this value override_name = '{0}_or_{1}_{2}'.format(activation_word, deactivation_word, option_value) line_generator = getattr(self, override_name, None) # If not available use a sensible default if line_generator is None: def _default_generator(is_activated): if is_activated: line = '--{0}-{1}'.format(activation_word, option_value) if activation_value is not None and activation_value( option_value): # NOQA=ignore=E501 line += '={0}'.format( activation_value(option_value)) return line return '--{0}-{1}'.format(deactivation_word, option_value) line_generator = _default_generator args.append(line_generator(activated)) return args def with_or_without(self, name, activation_value=None, variant=None): """Inspects a variant and returns the arguments that activate or deactivate the selected feature(s) for the configure options. This function works on all type of variants. For bool-valued variants it will return by default ``--with-{name}`` or ``--without-{name}``. For other kinds of variants it will cycle over the allowed values and return either ``--with-{value}`` or ``--without-{value}``. If activation_value is given, then for each possible value of the variant, the option ``--with-{value}=activation_value(value)`` or ``--without-{value}`` will be added depending on whether or not ``variant=value`` is in the spec. Args: name (str): name of a valid multi-valued variant activation_value (typing.Callable): callable that accepts a single value and returns the parameter to be used leading to an entry of the type ``--with-{name}={parameter}``. The special value 'prefix' can also be assigned and will return ``spec[name].prefix`` as activation parameter. Returns: list of arguments to configure """ return self._activate_or_not(name, 'with', 'without', activation_value, variant) def enable_or_disable(self, name, activation_value=None, variant=None): """Same as :meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without` but substitute ``with`` with ``enable`` and ``without`` with ``disable``. Args: name (str): name of a valid multi-valued variant activation_value (typing.Callable): if present accepts a single value and returns the parameter to be used leading to an entry of the type ``--enable-{name}={parameter}`` The special value 'prefix' can also be assigned and will return ``spec[name].prefix`` as activation parameter. Returns: list of arguments to configure """ return self._activate_or_not(name, 'enable', 'disable', activation_value, variant) run_after('install')(PackageBase._run_default_install_time_test_callbacks) def installcheck(self): """Searches the Makefile for an ``installcheck`` target and runs it if found. """ with working_dir(self.build_directory): self._if_make_target_execute('installcheck') # Check that self.prefix is there after installation run_after('install')(PackageBase.sanity_check_prefix) @run_after('install') def remove_libtool_archives(self): """Remove all .la files in prefix sub-folders if the package sets ``install_libtool_archives`` to be False. """ # If .la files are to be installed there's nothing to do if self.install_libtool_archives: return # Remove the files and create a log of what was removed libtool_files = fs.find(str(self.prefix), '*.la', recursive=True) with fs.safe_remove(*libtool_files): fs.mkdirp(os.path.dirname(self._removed_la_files_log)) with open(self._removed_la_files_log, mode='w') as f: f.write('\n'.join(libtool_files)) # On macOS, force rpaths for shared library IDs and remove duplicate rpaths run_after('install')(PackageBase.apply_macos_rpath_fixups)
class CudaPackage(PackageBase): """Auxiliary class which contains CUDA variant, dependencies and conflicts and is meant to unify and facilitate its usage. """ # FIXME: keep cuda and cuda_arch separate to make usage easier untill # Spack has depends_on(cuda, when='cuda_arch!=None') or alike variant('cuda', default=False, description='Build with CUDA') # see http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-feature-list # https://developer.nvidia.com/cuda-gpus variant('cuda_arch', description='CUDA architecture', values=spack.variant.any_combination_of( '20', '30', '32', '35', '50', '52', '53', '60', '61', '62', '70', '72', '75' )) # see http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#nvcc-examples # and http://llvm.org/docs/CompileCudaWithLLVM.html#compiling-cuda-code @staticmethod def cuda_flags(arch_list): return [('--generate-code arch=compute_{0},code=sm_{0} ' '--generate-code arch=compute_{0},code=compute_{0}').format(s) for s in arch_list] depends_on("cuda@7:", when='+cuda') # CUDA version vs Architecture depends_on("cuda@8:", when='cuda_arch=60') depends_on("cuda@8:", when='cuda_arch=61') depends_on("cuda@8:", when='cuda_arch=62') depends_on("cuda@9:", when='cuda_arch=70') depends_on("cuda@9:", when='cuda_arch=72') depends_on("cuda@10:", when='cuda_arch=75') depends_on('cuda@:8', when='cuda_arch=20') # There are at least three cases to be aware of for compiler conflicts # 1. Linux x86_64 # 2. Linux ppc64le # 3. Mac OS X # Linux x86_64 compiler conflicts from here: # https://gist.github.com/ax3l/9489132 arch_platform = ' arch=x86_64 platform=linux' conflicts('%gcc@5:', when='+cuda ^cuda@:7.5' + arch_platform) conflicts('%gcc@6:', when='+cuda ^cuda@:8' + arch_platform) conflicts('%gcc@7:', when='+cuda ^cuda@:9.1' + arch_platform) conflicts('%gcc@8:', when='+cuda ^[email protected]' + arch_platform) conflicts('%pgi@:14.8', when='+cuda ^cuda@:7.0.27' + arch_platform) conflicts('%pgi@:15.3,15.5:', when='+cuda ^[email protected]' + arch_platform) conflicts('%pgi@:16.2,16.0:16.3', when='+cuda ^cuda@8' + arch_platform) conflicts('%pgi@:15,18:', when='+cuda ^[email protected]:9.1' + arch_platform) conflicts('%pgi@:16', when='+cuda ^[email protected]:10' + arch_platform) conflicts('%clang@:3.4', when='+cuda ^cuda@:7.5' + arch_platform) conflicts('%clang@:3.7,4:', when='+cuda ^[email protected]:9.0' + arch_platform) conflicts('%clang@:3.7,4.1:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:3.7,5.1:', when='+cuda ^[email protected]' + arch_platform) conflicts('%clang@:3.7,6.1:', when='+cuda ^[email protected]' + arch_platform) # x86_64 vs. ppc64le differ according to NVidia docs # Linux ppc64le compiler conflicts from Table from the docs below: # https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.2/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.1/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/9.0/cuda-installation-guide-linux/index.html # https://docs.nvidia.com/cuda/archive/8.0/cuda-installation-guide-linux/index.html arch_platform = ' arch=ppc64le platform=linux' # information prior to CUDA 9 difficult to find conflicts('%gcc@6:', when='+cuda ^cuda@:9' + arch_platform) conflicts('%gcc@8:', when='+cuda ^[email protected]' + arch_platform) conflicts('%pgi', when='+cuda ^cuda@:8' + arch_platform) conflicts('%pgi@:16', when='+cuda ^cuda@:9.1.185' + arch_platform) conflicts('%pgi@:17', when='+cuda ^cuda@:10' + arch_platform) conflicts('%clang@4:', when='+cuda ^cuda@:9.0.176' + arch_platform) conflicts('%clang@5:', when='+cuda ^cuda@:9.1' + arch_platform) conflicts('%clang@6:', when='+cuda ^cuda@:9.2' + arch_platform) conflicts('%clang@7:', when='+cuda ^[email protected]' + arch_platform) # Intel is mostly relevant for x86_64 Linux, even though it also # exists for Mac OS X. conflicts('%intel@:14,16:', when='+cuda ^[email protected]') conflicts('%intel@:14,17:', when='+cuda ^[email protected]') conflicts('%intel@:14,18:', when='+cuda ^[email protected]:9.1') conflicts('%intel@17:18', when='+cuda ^[email protected]:') conflicts('%intel@19:', when='+cuda') # XL is mostly relevant for ppc64le Linux conflicts('%xl@:12,14:', when='+cuda ^cuda@:9.1') conflicts('%xl@:12,14:15,17:', when='+cuda ^[email protected]') conflicts('%xl@17:', when='+cuda ^[email protected]') # Mac OS X # platform = ' platform=darwin' # Apple XCode clang vs. LLVM clang are difficult to specify # with spack syntax. Xcode clang name is `[email protected]` # which precludes ranges being specified. We have proposed # rename XCode clang to `[email protected]` or even # `[email protected] as a possible fix. # Compiler conflicts will be eventual taken from here: # https://docs.nvidia.com/cuda/cuda-installation-guide-mac-os-x/index.html#abstract # Make sure cuda_arch can not be used without +cuda conflicts('~cuda', when='cuda_arch=20') conflicts('~cuda', when='cuda_arch=30') conflicts('~cuda', when='cuda_arch=32') conflicts('~cuda', when='cuda_arch=35') conflicts('~cuda', when='cuda_arch=50') conflicts('~cuda', when='cuda_arch=52') conflicts('~cuda', when='cuda_arch=53') conflicts('~cuda', when='cuda_arch=60') conflicts('~cuda', when='cuda_arch=61') conflicts('~cuda', when='cuda_arch=62') conflicts('~cuda', when='cuda_arch=70') conflicts('~cuda', when='cuda_arch=72') conflicts('~cuda', when='cuda_arch=75')