|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import copy |
| 4 | +import logging |
| 5 | +import os |
| 6 | +import re |
| 7 | +import sys |
| 8 | + |
| 9 | +import spack |
| 10 | +import spack.environment as ev |
| 11 | +from spack.provider_index import ProviderIndex |
| 12 | + |
| 13 | +from spack.extensions.stack.common import ALIASES |
| 14 | +from spack.extensions.stack.common import RED, RESET |
| 15 | +from spack.extensions.stack.common import get_preferred_compiler |
| 16 | + |
| 17 | + |
| 18 | +def get_compiler_name_and_version(string): |
| 19 | + compiler_name = string.replace("@=", "@").split("@")[0] |
| 20 | + try: |
| 21 | + compiler_version = string.replace("@=", "@").split("@")[1] |
| 22 | + except: |
| 23 | + compiler_version = None |
| 24 | + return (compiler_name, compiler_version) |
| 25 | + |
| 26 | + |
| 27 | +def get_compiler_choice(string): |
| 28 | + """Parse string for a Spack version 1 compiler dependency |
| 29 | + declaration. By intentionally not matching old (spack v0) |
| 30 | + compiler dependency declarations ("%gcc", "%oneapi", ...), |
| 31 | + we force updating the Spack configuration files to v1.""" |
| 32 | + COMPILER_CHOICE_REGEX_STRING = "^(%+)(" + \ |
| 33 | + "c=|" + \ |
| 34 | + "cxx=|" + \ |
| 35 | + "fortran=|" + \ |
| 36 | + "c,cxx=|" + \ |
| 37 | + "cxx,c=|" + \ |
| 38 | + "c,fortran=|" + \ |
| 39 | + "fortran,c=|" + \ |
| 40 | + "cxx,fortran=|" + \ |
| 41 | + "fortran,cxx=|" + \ |
| 42 | + "c,cxx,fortran=|" + \ |
| 43 | + "fortran,c,cxx=|" + \ |
| 44 | + "cxx,fortran,c=|" + \ |
| 45 | + "c,fortran,cxx=|" + \ |
| 46 | + "cxx,c,fortran=|" + \ |
| 47 | + "fortran,cxx,c=)(\S+)\s*$" |
| 48 | + COMPILER_CHOICE_REGEX = re.compile(COMPILER_CHOICE_REGEX_STRING) |
| 49 | + match = COMPILER_CHOICE_REGEX.match(string) |
| 50 | + if match: |
| 51 | + return match.group(3) |
| 52 | + return None |
| 53 | + |
| 54 | + |
| 55 | +def check_preferred_compiler(): |
| 56 | + """For an active environment, check that the preferred compiler |
| 57 | + is being used for all packages except those that explicitly |
| 58 | + request a different compiler. For the latter packages, check |
| 59 | + that the explicitly requested compiler is being used.""" |
| 60 | + |
| 61 | + logging.info("Configuring active spack environment ...") |
| 62 | + env_dir = ev.active_environment().path |
| 63 | + if not env_dir: |
| 64 | + raise Exception("No active spack environment") |
| 65 | + env = spack.environment.Environment(env_dir) |
| 66 | + spack.environment.environment.activate(env) |
| 67 | + logging.info(" ... environment directory: {}".format(env_dir)) |
| 68 | + |
| 69 | + # Get all specs and determine compilers |
| 70 | + specs = env.all_specs() |
| 71 | + if not specs: |
| 72 | + raise Exception(f"{RED}No specs found - did you run 'spack concretize'?{RESET}") |
| 73 | + q = ProviderIndex(specs=specs, repository=spack.repo.PATH) |
| 74 | + |
| 75 | + c_providers = q.providers_for("c") |
| 76 | + cxx_providers = q.providers_for("cxx") |
| 77 | + fortran_providers = q.providers_for("fortran") |
| 78 | + compilers = list(set(c_providers + cxx_providers + fortran_providers)) |
| 79 | + if not compilers: |
| 80 | + raise Exception(f"{RED}No compilers found{RESET}!") |
| 81 | + logging.info(f" ... compilers: {compilers}") |
| 82 | + |
| 83 | + # Determine the preferred compiler |
| 84 | + preferred_compiler = get_preferred_compiler(spack.config) |
| 85 | + (preferred_compiler_name, preferred_compiler_version) = get_compiler_name_and_version(preferred_compiler) |
| 86 | + logging.info(" ... preferred compiler: {}".format(preferred_compiler)) |
| 87 | + |
| 88 | + # Get package config to compare actual specs against the intended config |
| 89 | + package_config = spack.config.get("packages") |
| 90 | + |
| 91 | + logging.info("Checking all specs ...") |
| 92 | + errors = 0 |
| 93 | + for spec in specs: |
| 94 | + # If the spec has no compiler dependency, an exception will be thrown - ignore package |
| 95 | + try: |
| 96 | + compiler_name = spec.compiler.name |
| 97 | + compiler_version = spec.compiler.version if preferred_compiler_version else None |
| 98 | + except: |
| 99 | + logging.info(f" ... {spec.name}@{spec.version}/{spec.dag_hash(length=7)} has no compiler dependency") |
| 100 | + continue |
| 101 | + # If the spec compiler matches the preferred compiler for the environment, move on. |
| 102 | + # Note that this permits situations where a packages has an explicit preferred (but |
| 103 | + # not explicitly required) compiler, but Spack decides to use the preferred (and |
| 104 | + # different) compiler for the environment instead. |
| 105 | + if preferred_compiler_name == compiler_name and preferred_compiler_version == compiler_version: |
| 106 | + logging.info(f" ... {spec.name}@{spec.version}/{spec.dag_hash(length=7)} uses preferred compiler") |
| 107 | + else: |
| 108 | + spec_required_compiler_name = None |
| 109 | + spec_required_compiler_version = None |
| 110 | + spec_preferred_compiler_name = None |
| 111 | + spec_preferred_compiler_version = None |
| 112 | + for key, value in package_config[spec.name].items(): |
| 113 | + # To simplify parsing, turn scalar values into CommentedSeq of length 1 |
| 114 | + if isinstance(value, (str, bytes)): |
| 115 | + values = CommentedSeq([value]) |
| 116 | + else: |
| 117 | + values = value |
| 118 | + # Loop through all values to check for required or preferred compilers |
| 119 | + for entry in values: |
| 120 | + if key.lower() == "require": |
| 121 | + choice = get_compiler_choice(entry.lower()) |
| 122 | + # Not a compiler preference, carry on |
| 123 | + if not choice: |
| 124 | + continue |
| 125 | + # Check that the explicitly required compiler is a valid (existing) |
| 126 | + # compiler for this environment. This requirement may be relaxed in |
| 127 | + # the future if we start building compilers in spack environments. |
| 128 | + if any(choice in c for c in compilers): |
| 129 | + (spec_required_compiler_name, spec_required_compiler_version) = get_compiler_name_and_version(choice) |
| 130 | + elif key.lower() == "prefer": |
| 131 | + choice = get_compiler_choice(entry.lower()) |
| 132 | + # Not a compiler preference, carry on |
| 133 | + if not choice: |
| 134 | + continue |
| 135 | + # Check that the explicitly preferred compiler is a valid (existing) |
| 136 | + # compiler for this environment. This requirement may be relaxed in |
| 137 | + # the future if we start building compilers in spack environments. |
| 138 | + if any(choice in c for c in compilers): |
| 139 | + (spec_preferred_compiler_name, spec_preferred_compiler_version) = get_compiler_name_and_version(choice) |
| 140 | + # If we have a hard requirement for a compiler, we can stop scanning the spec package config |
| 141 | + if spec_required_compiler_name: |
| 142 | + break |
| 143 | + if spec_required_compiler_name == compiler_name and \ |
| 144 | + ( (not spec_required_compiler_version or not compiler_version) or \ |
| 145 | + (spec_required_compiler_version==compiler_version) ): |
| 146 | + logging.info(f" ... {spec.name}@{spec.version}/{spec.dag_hash(length=7)} uses explicitly required compiler") |
| 147 | + elif spec_preferred_compiler_name == compiler_name and \ |
| 148 | + ( (not spec_preferred_compiler_version or not compiler_version) or \ |
| 149 | + (spec_preferred_compiler_version==compiler_version) ): |
| 150 | + logging.info(f" ... {spec.name}@{spec.version}/{spec.dag_hash(length=7)} uses explicitly preferred compiler") |
| 151 | + else: |
| 152 | + errors += 1 |
| 153 | + logging.error(f" ... {RED}error: {spec.name}@{spec.version}/{spec.dag_hash(length=7)} does not use intended compiler\n" + \ |
| 154 | + f" check also that any explicit preferred/required compiler dependencies are using Spack v1 syntax{RESET}") |
| 155 | + if errors: |
| 156 | + raise Exception(f"{RED}Detected {errors} compiler mismatches!{RESET}") |
0 commit comments