Simplify the detection protocol for packages

Packages can implement “detect_version” to support detection
of external instances of a package. This is generally easier
than implementing “determine_spec_details”. The API for
determine_version is similar: for example you can return
“None” to indicate that an executable is not an instance
of a package.

Users may implement a “determine_variants” method for a package.
When doing external detection, executables are grouped by version
and each group results in a single invocation of “determine_variants”
for the associated spec. The method returns a string specifying
the variants for the package. The method may additionally return
a dictionary representing extra attributes for the package.

These will be stored in the spec yaml and can be retrieved
from self.spec.extra_attributes

The Spack GCC package has been updated with an implementation
of “determine_variants” which adds the following extra
attributes to the package: c, cxx, fortran
This commit is contained in:
Massimiliano Culpo
2020-07-31 13:07:48 +02:00
committed by Peter Scheibel
parent 193e8333fa
commit c0d490ffbe
11 changed files with 576 additions and 126 deletions

View File

@@ -2,10 +2,6 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack import *
import os
import re
@@ -28,23 +24,13 @@ class Automake(AutotoolsPackage, GNUMirrorPackage):
build_directory = 'spack-build'
executables = ['automake']
executables = ['^automake$']
@classmethod
def determine_spec_details(cls, prefix, exes_in_prefix):
exe_to_path = dict(
(os.path.basename(p), p) for p in exes_in_prefix
)
if 'automake' not in exe_to_path:
return None
exe = spack.util.executable.Executable(exe_to_path['automake'])
output = exe('--version', output=str)
if output:
match = re.search(r'GNU automake\)\s+(\S+)', output)
if match:
version_str = match.group(1)
return Spec('automake@{0}'.format(version_str))
def determine_version(cls, exe):
output = Executable(exe)('--version', output=str)
match = re.search(r'GNU automake\)\s+(\S+)', output)
return match.group(1) if match else None
def patch(self):
# The full perl shebang might be too long

View File

@@ -2,21 +2,18 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack import *
import re
import os
class Cmake(Package):
"""A cross-platform, open-source build system. CMake is a family of
tools designed to build, test and package software."""
tools designed to build, test and package software.
"""
homepage = 'https://www.cmake.org'
url = 'https://github.com/Kitware/CMake/releases/download/v3.15.5/cmake-3.15.5.tar.gz'
url = 'https://github.com/Kitware/CMake/releases/download/v3.15.5/cmake-3.15.5.tar.gz'
maintainers = ['chuckatkins']
executables = ['cmake']
executables = ['^cmake$']
version('3.18.1', sha256='c0e3338bd37e67155b9d1e9526fec326b5c541f74857771b7ffed0c46ad62508')
version('3.18.0', sha256='83b4ffcb9482a73961521d2bafe4a16df0168f03f56e6624c419c461e5317e29')
@@ -163,20 +160,10 @@ class Cmake(Package):
phases = ['bootstrap', 'build', 'install']
@classmethod
def determine_spec_details(cls, prefix, exes_in_prefix):
exe_to_path = dict(
(os.path.basename(p), p) for p in exes_in_prefix
)
if 'cmake' not in exe_to_path:
return None
cmake = spack.util.executable.Executable(exe_to_path['cmake'])
output = cmake('--version', output=str)
if output:
match = re.search(r'cmake.*version\s+(\S+)', output)
if match:
version_str = match.group(1)
return Spec('cmake@{0}'.format(version_str))
def determine_version(cls, exe):
output = Executable(exe)('--version', output=str)
match = re.search(r'cmake.*version\s+(\S+)', output)
return match.group(1) if match else None
def flag_handler(self, name, flags):
if name == 'cxxflags' and self.compiler.name == 'fj':

View File

@@ -2,16 +2,17 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack import *
from spack.operating_systems.mac_os import macos_version, macos_sdk_path
from llnl.util import tty
import glob
import itertools
import os
import re
import sys
import llnl.util.tty as tty
import spack.util.executable
from spack.operating_systems.mac_os import macos_version, macos_sdk_path
class Gcc(AutotoolsPackage, GNUMirrorPackage):
"""The GNU Compiler Collection includes front ends for C, C++, Objective-C,
@@ -269,6 +270,105 @@ class Gcc(AutotoolsPackage, GNUMirrorPackage):
build_directory = 'spack-build'
@property
def executables(self):
names = [r'gcc', r'[^\w]?g\+\+', r'gfortran']
suffixes = [r'', r'-mp-\d+\.\d', r'-\d+\.\d', r'-\d+', r'\d\d']
return [r''.join(x) for x in itertools.product(names, suffixes)]
@classmethod
def filter_detected_exes(cls, prefix, exes_in_prefix):
result = []
for exe in exes_in_prefix:
# clang++ matches g++ -> clan[g++]
if any(x in exe for x in ('clang', 'ranlib')):
continue
# Filter out links in favor of real executables
if os.path.islink(exe):
continue
result.append(exe)
return result
@classmethod
def determine_version(cls, exe):
version_regex = re.compile(r'([\d\.]+)')
for vargs in ('-dumpfullversion', '-dumpversion'):
try:
output = spack.compiler.get_compiler_version_output(exe, vargs)
match = version_regex.search(output)
if match:
return match.group(1)
except spack.util.executable.ProcessError:
pass
except Exception as e:
tty.debug(e)
return None
@classmethod
def determine_variants(cls, exes, version_str):
languages, compilers = set(), {}
for exe in exes:
basename = os.path.basename(exe)
if 'gcc' in basename:
languages.add('c')
compilers['c'] = exe
elif 'g++' in basename:
languages.add('c++')
compilers['cxx'] = exe
elif 'gfortran' in basename:
languages.add('fortran')
compilers['fortran'] = exe
variant_str = 'languages={0}'.format(','.join(languages))
return variant_str, {'compilers': compilers}
@classmethod
def validate_detected_spec(cls, spec, extra_attributes):
# For GCC 'compilers' is a mandatory attribute
msg = ('the extra attribute "compilers" must be set for '
'the detected spec "{0}"'.format(spec))
assert 'compilers' in extra_attributes, msg
compilers = extra_attributes['compilers']
for constraint, key in {
'languages=c': 'c',
'languages=c++': 'cxx',
'languages=fortran': 'fortran'
}.items():
if spec.satisfies(constraint, strict=True):
msg = '{0} not in {1}'
assert key in compilers, msg.format(key, spec)
@property
def cc(self):
msg = "cannot retrieve C compiler [spec is not concrete]"
assert self.spec.concrete, msg
if self.spec.external:
return self.spec.extra_attributes['compilers'].get('c', None)
return self.spec.prefix.bin.gcc if 'languages=c' in self.spec else None
@property
def cxx(self):
msg = "cannot retrieve C++ compiler [spec is not concrete]"
assert self.spec.concrete, msg
if self.spec.external:
return self.spec.extra_attributes['compilers'].get('cxx', None)
result = None
if 'languages=c++' in self.spec:
result = os.path.join(self.spec.prefix.bin, 'g++')
return result
@property
def fortran(self):
msg = "cannot retrieve Fortran compiler [spec is not concrete]"
assert self.spec.concrete, msg
if self.spec.external:
return self.spec.extra_attributes['compilers'].get('fortran', None)
result = None
if 'languages=fortran' in self.spec:
result = self.spec.prefix.bin.gfortran
return result
def url_for_version(self, version):
# This function will be called when trying to fetch from url, before
# mirrors are tried. It takes care of modifying the suffix of gnu